# Antwork Alternative for Tweet Automation | Tweet API Source: https://docs.xquik.com/alternatives/antwork Compare Antwork with Xquik for AI agent workflows, tweet search, follower exports, post tweets and replies, monitors, signed webhooks, and MCP. See examples.
For the complete documentation index, see llms.txt.
## Distinct Antwork Fit Antwork centers on AI-chat publishing across 8+ social platforms. Its differentiators are brand voice, campaign generation, scheduling, analytics, and MCP. Use this page for cross-platform publishing, not X-only data collection. Use this guide to decide whether Antwork or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Antwork pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team wants AI-chat-driven social publishing across 8+ platforms with MCP setup, brand voice, scheduling, analytics, and campaign review. You need publishing plus tweet search, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, and MCP in the same account. ## Source-backed Antwork scope Antwork's official home describes it as social media infrastructure that connects AI agents and workflows to social platforms for scheduling, publishing, and analytics. The home page lists LinkedIn, X, Instagram, Facebook, YouTube, TikTok, Threads, and Pinterest as supported platforms. It also shows MCP client setup for Claude Code, Claude Desktop, Cursor, ChatGPT, VS Code, Windsurf, Gemini CLI, OpenClaw, and JetBrains. The MCP setup example uses server URL `https://api.antwork.io/mcp`. The quickstart says setup uses OAuth in the browser and starts by creating an account, connecting at least one social account, and letting Antwork learn brand voice from existing content. Antwork's visible tool examples include `create_post`, `get_performance`, `get_brand_dna`, `list_social_accounts`, `schedule_post`, and `get_optimal_posting_times`. Official docs describe Antwork as an AI-powered social media management platform for teams to create, schedule, and publish content across 8+ platforms. Documented features include brand DNA extraction, AI content generation, 30+ post campaign generation, smart scheduling, media library, team collaboration, and analytics. The pricing page lists a Free plan at USD 0/month with 2 social accounts, 20 posts/month, 5 AI images/month, brand DNA and voice, and all MCP tools. Solo is USD 19/month with 5 social accounts, 100 posts/month, 50 AI images/month, 5 AI videos/month, brand DNA and voice, and all MCP tools. Grow is USD 49/month with 15 social accounts, no listed post-count cap, 200 AI images/month, 20 AI videos/month, brand DNA and voice, and all MCP tools. Scale is USD 99/month with 50 social accounts, no listed post-count cap, 500 AI images/month, 50 AI videos/month, brand DNA and voice, all MCP tools, and priority support. ## Current cost checkpoint Use Antwork units when the job is AI-chat social publishing across multiple platforms. Use Xquik units when the job needs X search, follower exports, write actions, media upload, DMs, monitors, signed webhooks, SDKs, exports, or MCP. | Task | Antwork unit to price | Xquik unit to price | | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | Create and schedule social posts from AI chat | Price by plan, social-account count, monthly post allowance, AI image allowance, and AI video allowance. | Use Xquik when the job is X-specific posting plus search, exports, media upload, DMs, monitors, or webhooks. | | Manage multiple social platforms | Free lists 2 social accounts; Solo lists 5; Grow lists 15; Scale lists 50. | Xquik focuses on tweet search results, follower exports, account actions & monitor events, write actions, monitors, webhooks, SDKs, exports, and MCP. | | Connect an AI agent | Antwork exposes MCP tools through `https://api.antwork.io/mcp` with OAuth setup. | Xquik exposes REST, SDK, webhook, and MCP handoffs for X tasks, with normalized pagination and credit billing. | | Analyze and schedule content | Antwork docs cover performance metrics, brand DNA, social-account status, scheduling, and posting-time suggestions. | Xquik account and keyword monitors produce stored events, signed webhooks, REST polling, and exportable records. | | Search tweets or export followers | Antwork's public scope centers on publishing, scheduling, analytics, brand voice, and MCP social management. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | ## Comparison | Area | Antwork | Xquik | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | AI-agent teams need cross-platform social publishing, scheduling, analytics, and brand-voice work from MCP clients. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | AI social publishing and management through MCP clients. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | LinkedIn, X, Instagram, Facebook, YouTube, TikTok, Threads, Pinterest, brand DNA, content generation, campaigns, scheduling, media, team review, and analytics. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Public plans run from Free to USD 99/month and differ by social accounts, posts, AI images, AI videos, and support. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for OAuth setup, MCP client installation, social-account connections, brand voice, review, scheduling, and analytics. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Antwork fits AI-chat social publishing across multiple social platforms. | Xquik fits teams when the task needs X search, followers, verified followers, extractions, monitors, webhooks, SDKs, and MCP. | | Agent use | MCP tools for drafting, scheduling, analytics, brand DNA, social accounts, and posting-time suggestions. | X tools for agents, dashboards, REST API calls, SDKs, and webhooks. | | Channel scope | 8+ social platforms and social media management workflows. | Focused tweet search results, follower exports, account actions & monitor events, write, and monitor tasks. | | Operational data | Publishing plans, social-account status, content metrics, brand voice, and campaign review. | Search, follower exports, monitors, draw records, and signed event delivery. | ## Operating model Compare Antwork and Xquik by output, cost, and handoff. Antwork fits AI-chat social publishing across multiple social platforms. Xquik fits teams when the task needs X search, followers, verified followers, extractions, monitors, webhooks, SDKs, and MCP. Agent use: MCP tools for drafting, scheduling, analytics, brand DNA, social accounts, and posting-time suggestions. Xquik: X tools for agents, dashboards, REST API calls, SDKs, and webhooks. Channel scope: 8+ social platforms and social media management workflows. Xquik: Focused tweet search results, follower exports, account actions & monitor events, write, and monitor tasks. Operational data: Publishing plans, social-account status, content metrics, brand voice, and campaign review. Xquik: Search, follower exports, monitors, draw records, and signed event delivery. ## Xquik value to test For agent and automation comparisons, test the actual handoff: can it search tweets, fetch users, run extractions, post, monitor, and receive webhook events from one API key? Choose Xquik when one API key must handle search, user fetches, extractions, posts, monitors, and webhook events. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm agent access, API behavior, connected-account actions, export formats, webhook payloads, and the handoff to your production system. Then compare seats, hosting, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Antwork Trial Evidence Test one publishing request and one X research request. Use the same account, topic, and review window. For Antwork, record the agent instruction, social action, approval, and published result. For Xquik, record the exact REST or MCP call. Keep Tweet IDs, user IDs, cursors, monitor IDs, and write receipts. Do not compare a broad agent prompt with one narrow API call. Compare the evidence each tool hands to the next step. The tools may work together. Keep Antwork for coordinated agents. Keep Xquik for traceable X operations. ## Migration path Start with one repeated task. Validate that Xquik can run the data collection, account action, export, or webhook handoff before moving more agent or dashboard work. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Compare Antwork plans with Xquik by platform breadth, agent support, and X search, follower exports, writes, monitors, and webhooks. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. # Xquik Vs Apify for Tweet Scraping & Follower Exports Source: https://docs.xquik.com/alternatives/apify Compare Apify with Xquik for X/Twitter scraping, tweet search, follower exports, datasets, monitors, webhooks, MCP, and API integrations. Compare API coverage.
For the complete documentation index, see llms.txt.
Compare Apify, Xquik, and Xquik's Apify Actors. Match Actor inputs, dataset rows, exports, and automation needs for one X/Twitter job. This is a factual comparison and migration guide. Verify current Apify actor pricing, actor inputs, dataset exports, and platform terms on the official Apify pages before buying. ## Quick answer You need a broad web scraping platform or Actor marketplace. Run custom scrapers, scheduled Actors, datasets, and exports across many websites. You need focused X API tasks: tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, MCP, and dashboard tools. You want Apify-native Actor runs and datasets for focused X data jobs. Keep the option to move deeper workflows to Xquik. ## Source-backed Apify scope Apify's official docs define Actors as serverless automation programs. Each Actor accepts structured JSON input and can produce structured output. Choose an Actor, start its run, then read its dataset items. The dataset docs define each run's default dataset as append-only storage. Store web scraping, crawling, or processing results there. Export table-like dataset rows as `json`, `jsonl`, `csv`, `html`, `xlsx`, `xml`, or `rss`. Apify's Store API docs say `/v2/store` lists public Actors. It supports `username`, `search`, `limit`, `offset`, `sortBy`, `category`, `pricingModel`, and `responseFormat=agent`. The response includes stats and pricing metadata. Verify costs on each Actor page. Apify webhook docs describe webhooks as system-event actions. Current webhook actions send a POST HTTP request to the configured URL. Actor run events include created, succeeded, failed, aborted, timed out, and resurrected states. ## Xquik on Apify The public Xquik profile currently shows 8 Actors. This Actor extracts tweets, engagement metrics, author profiles, and media, then writes rows to an Apify dataset. Apify lists this Actor for followers, following, verified followers, list members, list subscribers, and community members. Export replies beneath one tweet as dataset rows. Export ranked trends for locations or WOEIDs. Export profiles, timelines, media, likes, and account relationships. Export community details, posts, members, moderators, or search results. Export list posts, members, or followers. Search profiles and apply documented account filters before export. Store badges, ranking positions, user counts, and run totals change. Verify the current Xquik Apify profile or Store API before citing marketplace placement. ## Apify Actor Handoff Use Xquik's Apify Actors when your team already receives data through Apify datasets. Move the same job to Xquik REST, exports, webhooks, or MCP when you need tighter API control, account actions, monitors, or agent access. | Job | Start on Apify | Dataset handoff | Move deeper to Xquik when | | ------------------------------------------------------------------------------------------------------------------ | --------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | Scrape tweets by keyword, hashtag, profile timeline, With Replies tab, account date window, URL, or batch tweet ID | Run `xquik/x-tweet-scraper` | Tweet rows with text, IDs, engagement metrics, author profiles, and media; export JSON, CSV, or XLSX | You need `GET /x/tweets/search`, `tweet_search_extractor`, pagination, 1-second monitors, signed webhooks, SDKs, or MCP | | Scrape followers, following, verified followers, or list members | Run `xquik/x-follower-scraper` | User rows with profile fields and filter-ready metadata; export JSON, CSV, or XLSX | You need follower exports from the API, CRM handoff, repeat syncs, or downstream workflow ownership | | Export replies beneath one tweet | Run `xquik/x-reply-scraper` | Reply rows with author, text, media, and engagement fields | You need direct cursor pagination or durable extraction jobs | | Read trends by location | Run `xquik/x-trends-scraper` | Ranked trend rows with query, volume, and location fields | You need direct trend API responses or app-owned scheduling | | Export profile data and related timelines | Run `xquik/x-profile-scraper` | Profile, tweet, media, like, follower, and following rows | You need separate endpoint calls or per-resource pagination control | | Export community data | Run `xquik/x-community-scraper` | Community, tweet, member, moderator, or search rows | You need direct community endpoints and response envelopes | | Export list data | Run `xquik/x-list-scraper` | List tweet, member, or follower rows | You need direct list endpoints and cursor ownership | | Search users | Run `xquik/x-user-search-scraper` | Filtered user profile rows | You need direct user search pagination or SDK types | For profile reply runs, use explicit `profileReplies` mode. You can also use a `/with_replies` profile URL. Match reply rows against `GET /x/users/{id}/replies`. Match profile timeline rows against `GET /x/users/{id}/tweets`. Use this mapping when moving jobs from Apify datasets to Xquik REST. ### Actor output contract to compare When testing Xquik on Apify, inspect dataset rows before comparing cost or migrating the same job to REST. `xquik/x-tweet-scraper` can write tweet rows, engagement user rows, article rows, and diagnostic rows. Keep `resultType`, `sourceTweetId`, `engagementMode`, author profile fields, media URLs, and article fields when loading the dataset into a warehouse. `xquik/x-follower-scraper` writes compact user rows by default. Use full or raw output modes for optional profile metadata and raw profile objects. They can also include source targets, relations, URLs, or overlap fields. Use Apify's max cost per run or API `maxTotalChargeUsd` as the hard spend cap. Use `maxItems` only when you want a smaller row cap than the budget allows. Empty, invalid, filtered, or zero-output runs can write one diagnostic row. The row uses `resultType: "diagnostic"`. Its status can be `no-input`, `invalid-input`, or `zero-output`. ### Date-window tweet scraping on Apify For account backfills, pass a Search Terms value. Example: `from:username since:2026-05-01 until:2026-05-02`. You can also use `from`, `since`, `until`, `since_time`, and `until_time`. The X Tweet Scraper Actor applies the account and time bounds before writing dataset rows. `maxItems` still caps the run or each `searchTerms` entry. Before production use, open the current Actor page. Verify inputs, pricing, event limits, dataset fields, and export formats. Call the public Store API at `https://api.apify.com/v2/store?username=xquik&limit=20&responseFormat=agent`. It lists current Xquik Actors, badges, categories, ratings, and stats. ## Comparison | Area | Apify | Xquik | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Teams need broad web automation, custom Actors, Actor Store tools, datasets, scheduled runs, and platform-level scraping infrastructure. | Teams need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Actor platform, Store marketplace, datasets, API clients, scheduling, monitoring, and MCP for web scraping jobs. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | X/Twitter path | Choose an Actor, pass its input, wait, then read dataset items. Browse the Xquik profile for the current catalog. | Call a documented REST endpoint, dashboard tool, SDK, export, webhook, or MCP tool. | | Returned data | Actor run objects, datasets, key-value stores, and export formats such as JSON, CSV, XLSX, XML, or RSS. | API responses, CSV/JSON/XLSX exports, monitor events, webhook payloads, action logs, and MCP responses. | | Cost model | Actor pricing can be pay per event, pay per usage, or rental, with platform and dataset usage details to verify per Actor. | Starter is USD 20/month with 140,000 included credits. Top-ups are USD 0.00015/credit, webhook management is free, and active monitors bill only while enabled. | | Integration effort | Select an Actor, review its input schema, call Apify API or SDK clients, track runs, read datasets, and handle actor-specific changes. | Start with a dashboard tool, then automate the same task through REST, webhooks, SDKs, exports, or MCP. | | Summary | Apify fits work across many websites or custom Actors. | Xquik fits tweet search, profile lookup, follower and reply exports, timelines, account actions, monitors, webhooks, SDKs, and agents. | ## Operating Model Apify starts with an Actor run and returns dataset items. Xquik starts with a documented X task and returns API, export, webhook, or MCP data. Test request shape, fields, pagination, export format, delivery, and retries end to end. ## Trial checklist Use one job: tweet search, follower export, account monitor, media upload, direct-message history, or webhook delivery. On Apify, select the Actor, run it, wait for completion, and read dataset items. On Xquik, call the endpoint or dashboard tool and inspect the returned records. Confirm where the output goes next: dataset, CSV, JSON, XLSX, webhook, SDK call, MCP tool, queue, CRM, or warehouse. Compare per-Actor pricing, platform usage, dataset access, Xquik credits, active monitor billing, and engineering time for retries, storage, and alerts. ## Migration path Do not migrate a general scraping stack first. Move one X/Twitter job that has a clear output contract and downstream owner. 1. Export the same tweet or follower sample from Apify and Xquik. 2. Compare IDs, usernames, timestamps, text fields, engagement fields, media links, pagination, and error states. 3. Replace the run polling step with a REST call, export, or signed webhook when Xquik already returns the fields your downstream system needs. 4. Keep Apify for broad web scraping jobs that do not belong to Xquik. ## Official sources to verify Verify Actor runs, datasets, API clients, retries, and export formats. Verify that the Store lists public Actors and how Store discovery works. Verify pay-per-event, pay-per-usage, rental, and dataset usage details. Verify current X/Twitter actor capabilities, maintainer, pricing, inputs, and output fields. Verify the current Xquik Actor count, user stats, run success rate, and listed public Actors. Verify current Xquik Actor badges, categories, ratings, and Store stats through the public Store API. ## Xquik next steps Review authentication, endpoint groups, response conventions, pagination, and errors. Estimate costs, run jobs, paginate results, and export CSV, JSON, or XLSX files. Map tweet monitoring, signed webhooks, MCP agents, follower exports, and tweet composition to API calls. Check included credits, top-ups, free operations, and active monitor billing. # Audiense Alternative for X Audience & Follower APIs Source: https://docs.xquik.com/alternatives/audiense Compare Audiense with Xquik for audience intelligence, influencer discovery, tweet search, follower exports, monitors, signed webhooks, API access, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Audiense or Xquik fits a specific X job: audience intelligence, influencer discovery, tweet search, follower export, account or keyword monitoring, signed webhooks, API access, and agent handoff. This is a factual comparison and migration guide. Verify current Audiense pricing, access rules, and product terms on the official site before buying. ## Quick answer You need audience segmentation, influencer discovery, X marketing analytics, campaign planning, or social insight reports. You need tweet search, follower exports, raw X records, write actions, 1-second monitors, signed webhooks, SDKs, or MCP. ## Source-backed Audiense scope Audiense's official Insights page describes audience intelligence for customer insights, creative decisions, influencer discovery, consumer segments, cultural insights, affinities, demographics, interests, personas, advertising targeting, SEO and keyword research, content ideation, and influencer outreach. Audiense's official Audience Intelligence page describes Social Intelligence with Audiense Insights and Affinio, Digital Intelligence through Soprism, and Demand Intelligence. Its current public pricing page lists a Social Intelligence Insights monthly plan with 5 reports per month, an annual plan with 60 reports per year, onboarding, a dedicated account manager, and refresher trainings. Audiense's help pages describe compliant public and partner data sources, report exports, segment member exports, influencer exports, PDF and PPT report downloads, targeting-pack downloads, and influencer filters for country, bio keywords, categories, content creators, influencer type, and account type. Paid Audience Insights users can export influencer lists to XLS or add them to an Audiense Connect audience. ## Comparison | Area | Audiense | Xquik | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Insight, marketing, and influencer teams need audience segmentation, X marketing analytics, social affinities, and influencer lists. | Teams that need tweet search, follower exports, raw X records, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Audience intelligence and X marketing analytics. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Audiense official pages describe audience segments, cultural insights, affinities, demographics, interests, personas, influencer discovery, report exports, audience member XLS exports, influencer XLS exports, and targeting-pack downloads. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Check seat count, contract minimums, add-ons, onboarding fees, and implementation time. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for procurement, admin setup, approval flows, reporting needs, and developer handoffs. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Audiense focuses on audience intelligence, segmentation, influencer discovery, and social insight work. | Xquik emphasizes direct tweet and profile records, follower exports, monitor events & webhooks, writes, monitoring, extractions, account monitors, and developer tools. | | Primary value | Audience analysis, segmentation, influencer lists, and campaign insight. | Operational tweet and profile records, follower exports, monitor events & webhooks, writes, exports, monitoring, and API handoff. | | Outputs | Insight reports, audience segments, influencer lists, and XLS exports on supported plans. | Exports, API responses, webhooks, MCP responses, and action logs. | | Team fit | Research, marketing, influencer, and intelligence teams. | Builders and operators automating X tasks. | ## Audience & influencer handoff Use this section when the search intent is "Audiense alternative for influencer discovery", "X audience intelligence", "export X followers", or "turn X audiences into CRM data". | Job | Audiense path | Xquik path | Compare | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | Segment an audience | Build an Audiense Insights report and review segments, affinities, demographics, interests, and sources of influence. | Export followers, following, verified followers, tweet search results, or mention rows, then analyze them in your own system. | Audiense gives insight reports. Xquik gives raw records and repeatable API pulls. | | Find influencers | Use Audiense influencer views, filters, affinity sorting, uniqueness sorting, and paid-plan XLS export. | Use follower exports, tweet search, verified follower exports, and engagement fields to build your own scoring model. | Audiense prioritizes influencer discovery. Xquik prioritizes data ownership and downstream automation. | | Monitor campaign conversation | Use Audiense with social listening or conversation-based reports to understand who is behind a topic. | Use 1-second keyword monitors, tweet search exports, signed webhooks, and `GET /events` for stored records. | Audiense explains audience composition. Xquik delivers event payloads and exports. | | Hand off to a CRM or warehouse | Export Audiense report outputs or influencer lists where supported. | Export CSV/JSON/XLSX or paginate `GET /extractions/{id}` with `nextCursor`. | Xquik fits scheduled imports, queues, and API pipelines. | ## Operating model Compare Audiense and Xquik by output, cost, and handoff. Audiense focuses on audience intelligence, segmentation, influencer discovery, and social insight work. Xquik emphasizes direct tweet and profile records, follower exports, monitor events & webhooks, writes, monitoring, extractions, account monitors, and developer tools. Primary value: Audience analysis, segmentation, and influencer discovery. Xquik: Operational tweet and profile records, follower exports, monitor events & webhooks, writes, exports, and monitoring. Outputs: Insight reports, segments, and influencer lists. Xquik: Exports, API responses, webhooks, MCP responses, and action logs. Team fit: Research, marketing, influencer, and intelligence teams. Xquik: Builders and operators automating X tasks. ## Xquik value to test For audience-intelligence comparisons, separate insight work from the exact X records your system needs: search tweets, export followers, inspect verified followers, monitor keywords, post actions, and send webhooks. Choose Xquik when the X records and handoff matter more than audience reports. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm ownership, approvals, audit needs, seat requirements, export format, and API behavior. Decide whether the task needs a broad suite or focused tweet search, profile lookup, follower exports, reply scraping & account actions. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Do not move the entire social stack first. Pick an X-only task, run it beside the existing suite, and confirm reporting and compliance needs before broad rollout. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Check Audiense public pricing or sales terms against Xquik plan needs. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. Verify current audience segmentation, influencer discovery, and insight-report features. Verify current reports, onboarding, account support, pricing, and plan terms. Verify current audience data sources, enrichment, integrations, and methodology. Verify current member, segment, influencer, report, and targeting-pack export options. Verify influencer filters, affinity and uniqueness sorting, and export behavior. # Black Magic Alternative for Twitter Analytics Source: https://docs.xquik.com/alternatives/black-magic Compare Black Magic Twitter analytics, CRM, scheduling, and engagement reports with Xquik tweet search, follower exports, monitors, webhooks, SDKs, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Black Magic or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Black Magic pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team needs Twitter/X analytics, creator CRM, reply search, reminders, reports, and tweet/thread scheduling inside Twitter. You need publishing plus tweet search, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, and MCP in the same account. ## Source-backed Black Magic scope Black Magic's official home positions the product around Twitter analytics, engagement growth, Twitter CRM, and scheduling plus publishing. It lists browser extensions for Chrome, Firefox, and Safari plus iOS and Android apps. The home page says the analytics dashboard provides insights and metrics, tracks tweet performance over time, compares tweets against account averages, tracks consistency, followers, engagements, and reports why a tweet takes off. The relationship and CRM section says Black Magic can sync Twitter Lists, track whether a person liked, retweeted, or replied to previous tweets, write private notes, set reminders for DMs or follow-up, see past interactions, and organize tweets into categories. The publishing section says creators can schedule tweets and threads, identify Most Engaging Hours, and get tweet inspirations. The reporting section lists daily or weekly email reports, tweet performance reports, record-breaking tweets, new notable followers, and account summaries. The pricing page says prices are in USD, subscriptions are tied to one Twitter account, annual plans save 3 months, and users can cancel subscriptions themselves. Annual billing lists Personal at USD 16.25/month with USD 195 billed annually, Professional at USD 32.41/month with USD 389 billed annually, and Business at USD 124.91/month with USD 1499 billed annually. The pricing comparison says Professional includes engagement tracking, active followers tracking, real-time tweet metrics, engagement heatmap, tweet replies search, quick reply, schedule tweets, schedule threads, browser extensions, web portal, 3rd-party integrations, mobile apps, and multi-account billing. Business adds priority support, data export, and custom setup plus reports. The pricing page also says extra Twitter accounts are not included in Personal. Professional lists additional accounts at USD 19.99/month per account or USD 179.91 annually, and Business lists additional accounts at USD 69.99/month per account or USD 629.91 annually. ## Current cost checkpoint Use Black Magic units when the job is creator analytics, relationship tracking, scheduling, and reporting inside Twitter. Use Xquik units when the job needs X search, follower exports, write actions, media upload, DMs, monitors, signed webhooks, SDKs, exports, or MCP. | Task | Black Magic unit to price | Xquik unit to price | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | Track creator performance | Price by plan, analytics depth, real-time metrics, report needs, and whether data export is required. | Use Xquik when analytics must pair with tweet search, follower exports, stored events, webhooks, or API handoff. | | Manage creator relationships | Price CRM features such as private notes, favorite people, reminders, past interactions, reply search, and quick reply. | Use Xquik when relationships need DM history, follower exports, account monitors, keyword monitors, CSV/JSON/XLSX files, or SDK calls. | | Schedule tweets and threads | Personal does not list schedule tweets or schedule threads; Professional and Business include them. | Xquik create-tweet actions cost 30 credits per tweet or reply, and media uploads cost 10 credits per upload call. | | Add more accounts | Professional and Business list different per-account add-on prices; validate the current billing interval before buying. | Xquik account actions, monitors, exports, webhooks, SDKs, and MCP use the same credit model. | | Search tweets or export followers | Black Magic's public scope centers on analytics, CRM, scheduling, reports, browser extensions, and mobile apps. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | ## Comparison | Area | Black Magic | Xquik | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Creators need Twitter/X analytics, CRM, reminders, reports, reply search, and tweet/thread scheduling. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Creator analytics, CRM, scheduling, and reporting tool for Twitter. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Analytics, tweet metrics, engagement tracking, past interactions, private notes, reminders, reply search, quick reply, scheduling, browser extensions, mobile apps, and reports. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Public annual plans list Personal, Professional, and Business from USD 16.25/month to USD 124.91/month, with per-account add-ons on higher plans. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for browser extension, mobile app, account add-ons, reporting, export requirements, CRM limits, and scheduling tier. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Black Magic focuses on creator analytics, Twitter CRM, reports, and scheduling inside Twitter. | Xquik adds tweet search, follower exports, write actions, monitors, signed webhooks, and API access. | | Core focus | Creator relationship, audience context, and tweet performance. | Tweet search, profile lookup, follower and reply exports, timelines, monitors, and account actions. | | Data movement | Product analytics, CRM views, email reports, and Business data export. | Portable CSV, JSON, XLSX, Markdown, and API responses. | | Automation | Scheduling, reminders, quick reply, reports, and integrations. | Read and write tools through dashboard and API. | ## Operating model Compare Black Magic and Xquik by output, cost, and handoff. Black Magic focuses on creator analytics, Twitter CRM, reports, and scheduling inside Twitter. Xquik adds tweet search, follower exports, write actions, monitors, signed webhooks, and API access. Core focus: Creator relationships, audience context, and tweet performance. Xquik: Tweet search, profile lookup, follower and reply exports, timelines, monitors, and account actions. Data movement: Product analytics, CRM views, email reports, and Business data export. Xquik: Portable CSV, JSON, XLSX, Markdown, and API responses. Automation: Scheduling, reminders, quick reply, reports, and integrations. Xquik: Read and write tools through dashboard and API. ## Xquik value to test For publishing comparisons, test what happens before and after the post: find source tweets, upload media, send DMs, export replies, monitor keywords, and notify downstream tools. Choose Xquik when publishing must include that data loop. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm draft flow, media handling, scheduling needs, export formats, API access, and whether data or monitoring matters after publishing. Then compare seats, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Black Magic Trial Evidence Test one creator relationship review and one export. Use the same accounts and review window. For Black Magic, record the relationship view, notes, and operator decision. For Xquik, record followers, replies, profiles, and monitor events. Keep export rows and stable IDs. Do not compare relationship context with raw row volume. Compare support for the intended decision and evidence handoff. The tools may work together. Keep Black Magic for relationship context. Keep Xquik for structured X workflows. ## Migration path Start with one publishing or reporting task. Keep the content calendar stable while you validate exports, API access, monitoring, and alerts in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Review Black Magic public pricing and Xquik pricing before choosing. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. # Brandwatch Alternative for Social Listening Source: https://docs.xquik.com/alternatives/brandwatch Compare Brandwatch social listening and consumer intelligence with Xquik tweet search, follower exports, account monitors, signed webhooks, REST APIs, and MCP.
For the complete documentation index, see llms.txt.
## Distinct Brandwatch Fit Brandwatch centers on enterprise social listening and consumer intelligence across channels. Its outputs include dashboards, segments, reports, alerts, and suite exports. Use this page for broad research programs, not an X-only API. Use this guide to decide whether Brandwatch or Xquik fits the X part of a social listening, consumer intelligence, or social media management workflow: search tweets, export followers, monitor accounts or keywords, send webhooks, and hand results to apps or agents. This is a factual comparison and migration guide. Verify current Brandwatch pricing, access rules, and product terms on the official site before buying. ## Quick answer You need a broad social listening, consumer intelligence, audience research, publishing, engagement, or analytics suite across teams and channels. You need X-specific records in code: tweet search, follower exports, 1-second monitors, signed webhooks, SDKs, MCP, and CSV/JSON/XLSX exports. ## Source-backed Brandwatch scope Brandwatch's official consumer intelligence pages describe a suite for searching conversations, segmenting audiences, analyzing trends with AI, and sharing insights through alerts and reports. Their public pages also describe historical and real-time data coverage, official firehose access to Twitter, Tumblr, and Reddit, dashboards, audience demographics, influencers, image analysis, Signals alerts, Excel/PPT/PDF exports, and Brandwatch API access. Brandwatch's official social media management pages describe a content calendar, publishing workflows, approval flows, an Engage inbox, sentiment and spam detection, helpdesk integration, campaign reporting, and social listening in one suite. Use those official pages to verify the broad-suite side of the comparison. Use Xquik's API reference and workflow guides to verify the X-specific side: tweet search, follower exports, monitors, webhooks, exports, SDKs, and MCP. ## Comparison | Area | Brandwatch | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Marketing, insights, or social teams need social listening, consumer intelligence, publishing, engagement, and analytics in a broader suite. | Operators and developers need X records, account actions, monitor events, exports, webhooks, SDKs, and MCP without a broad social-suite rollout. | | Product type | Consumer intelligence and social media management suite. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | X task coverage | Brandwatch official pages describe social listening, consumer research, publishing, engagement, dashboards, reports, alerts, exports, and Brandwatch API access. Verify current X access, export limits, API scope, and contract requirements before buying. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Output to test | Listening reports, audience segments, dashboards, alerts, exports, and team workflows. | Tweet records, user records, follower rows, monitor events, signed webhook payloads, CSV/JSON/XLSX exports, SDK calls, and MCP tool results. | | Pricing & value | Check seat count, package scope, add-ons, export needs, API access, onboarding, and implementation time. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for suite setup, team permissions, dashboards, alerts, reporting, and downstream export ownership. | Start from a dashboard task, then automate the same job through REST, webhook, SDK, export, or MCP when it needs to run repeatedly. | ## Operating model Compare Brandwatch and Xquik by the job you need to ship. Brandwatch is useful when the organization needs a broad social listening or consumer intelligence suite. Xquik is useful when the X part of the job needs direct records, repeatable API calls, exports, signed webhooks, or agent access. Brandwatch: social listening, consumer intelligence, audience research, publishing, and team analytics. Xquik: Tweet search, follower exports, monitor tweets, and X API handoff. Brandwatch: suite setup, seats, permissions, dashboards, and reports. Xquik: API key, dashboard tools, REST calls, SDKs, webhooks, and MCP. Brandwatch: reports, segments, alerts, and suite exports. Xquik: Tweet JSON, follower CSV/XLSX, webhook events, SDK responses, and MCP results. ## Xquik value to test For social listening comparisons, keep the trial focused on the exact tweet, profile, follower, reply & timeline workflow. Do not compare suite labels. Compare returned records, export format, webhook delivery, API behavior, and cost. Start with tweet search, follower exports, monitor events, signed webhook payloads, CSV/JSON/XLSX exports, SDK calls, or MCP tool results. Call [Search Tweets](/api-reference/x/search-tweets) or run an extraction to collect tweets for a keyword, account, list, community, reply thread, quote chain, or mention query. Run follower exports through dashboard tools, extraction jobs, REST endpoints, or SDKs, then hand CSV, JSON, XLSX, Markdown, or paginated JSON to the next system. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. Use 128 REST operations, 10 SDKs, MCP, API keys, and pay-per-use read endpoints when the workflow needs code, agents, or repeatable jobs. ## What to verify in a trial Pick one real query: monitor a brand account, search tweets for a campaign term, export followers, or send new matching tweets to a webhook. Compare tweet IDs, author IDs, timestamps, text, metrics, media links, pagination, export fields, webhook signatures, and error handling. Compare Brandwatch package requirements with Xquik credits, active monitor billing, top-ups, and the engineering time needed to keep the workflow running. ## Migration path Do not migrate the full listening program first. Start with one X-only workflow and one downstream owner. 1. Use Brandwatch for broad listening dashboards and team reporting if that is already the operating model. 2. Use Xquik for the X task that needs API output: tweet search, follower export, monitor tweets, webhook delivery, SDK calls, or MCP. 3. Keep both systems side by side until the Xquik output matches the fields, freshness, and handoff format the downstream system needs. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Export tweet search results to CSV, JSON, XLSX, Markdown, or paginated JSON. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. Verify current social listening, data coverage, AI analysis, export, and API claims. Verify dashboards, demographics, influencers, image analysis, exports, and Signals alerts. Verify publishing, approval, engagement inbox, sentiment, and helpdesk workflow claims. # Buffer Alternative for Tweet Scheduling | Tweet API Source: https://docs.xquik.com/alternatives/buffer Compare Buffer with Xquik for social media scheduling, tweet search, follower exports, post tweets, monitor tweets, webhooks, SDKs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Buffer or Xquik fits a social media scheduling workflow: plan posts, schedule threads, manage comments, analyze posts, search tweets, export followers, monitor accounts or keywords, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Buffer pricing, supported channels, scheduled-post limits, approval workflows, Community features, analytics exports, and product terms on the official site before buying. ## Quick answer You need a social content calendar, queue, visual schedule, AI-assisted post drafts, comment replies, analytics, and team approvals across many channels. You need the X part in code: tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, or MCP. ## Source-backed Buffer scope Buffer's official pricing page lists a Free plan for up to 3 channels with 10 scheduled posts per channel, Essentials at USD 5/channel/month billed yearly, Team at USD 10/channel/month billed yearly, advanced analytics, Community inbox, hashtag manager, first-comment scheduling, unlimited team members on Team, access levels, and content approval workflows. Buffer's help center says paid-plan scheduling is subject to a 5,000-post fair-use cap per channel. Buffer's official Publish pages describe supported channels for Bluesky, Facebook, Google Business Profile, Instagram, LinkedIn, Mastodon, Pinterest, Threads, TikTok, X, and YouTube. They also describe queues, calendars, channel-specific customization, threads, carousels, videos, first-comment scheduling on paid plans, AI Assistant, and approval workflows. Buffer's official Community pages describe comment management across Instagram, Facebook, LinkedIn, Threads, Bluesky, X, TikTok, Google Business Profile, YouTube, and Mastodon, with notifications, filters and sorting, comment score, saved replies, AI replies, direct replies from Buffer, and turning comments into posts. ## Comparison | Area | Buffer | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Creators, small businesses, agencies, or teams need one queue for multi-channel posts, comment replies, analytics, and content approvals. | Teams need tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Social media scheduling, publishing, analytics, community, and collaboration platform. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Buffer's official pages describe supported channels, queues, calendars, channel-specific posts, threaded posts, AI Assistant, first-comment scheduling, hashtag manager, Community replies, notifications, filters, comment score, saved replies, AI replies, and approval workflows. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Official pricing currently lists Free for 3 channels with 10 scheduled posts per channel, Essentials at USD 5/channel/month yearly, and Team at USD 10/channel/month yearly with unlimited team members and approval workflows. | Xquik starts at USD 20/month with 140,000 included credits. Tweet search and follower exports cost 1 credit/result. Common X write calls cost 10 credits/call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Connect channels, plan queues, manage scheduled posts, reply to comments, review analytics, and use no-code integrations for downstream handoff. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Buffer helps teams plan, schedule, publish, reply, analyze, and approve social posts across channels. | Xquik is focused on X records, account actions, monitors, API access, signed webhooks, and export files. | | Channel scope | Multi-channel social scheduling, community replies, analytics, and approvals. | Tweet search, follower exports, post actions, media uploads, DMs, monitors, SDKs, MCP, and webhooks. | | API use case | Social publishing calendar and comment workflow. | API calls for extraction, account actions, monitors, exports, and webhooks. | | Exports | Analytics reports for post and channel performance. | Raw tweet, profile, follower & reply exports plus account-action results. | ## Operating model Compare Buffer and Xquik by output, cost, and handoff. Buffer helps teams plan, schedule, publish, reply, analyze, and approve social posts across channels. Xquik is focused on X records, account actions, monitors, API access, signed webhooks, and export files. Buffer: multi-channel content calendar, queue, comment replies, analytics, and approvals. Xquik: self-serve tweet, profile, follower, reply & account actions with API access. Buffer: channels, queues, scheduled posts, Community replies, reports, and team approvals. Xquik: API keys, webhooks, SDKs, exports, and MCP. Buffer: scheduled posts, comment workflows, analytics reports, and approvals. Xquik: Tweet JSON, follower CSV/XLSX, monitor events, and webhook payloads. ## Current cost checkpoint Use Buffer when the main job is a content calendar across many social channels. Use Xquik when the job needs tweet search, follower exports, media posts & account monitors, exports, monitors, connected-account actions, SDKs, or MCP. | Task | Buffer unit to price | Xquik unit to price | | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Schedule 3 low-volume social channels | Free supports up to 3 channels and 10 scheduled posts per channel. | Create tweet or reply costs 30 credits/call. Xquik publishes to connected X accounts only. | | Schedule 5 active channels | Essentials yearly pricing is USD 5/channel/month, so 5 channels price at USD 25/month. | Xquik Starter is USD 20/month with 140,000 included credits for X tasks. Use it when the work is X-only and needs data or automation around publishing. | | Run team approvals | Team yearly pricing is USD 10/channel/month and includes unlimited team members plus content approval workflows. | Xquik does not replace a multi-channel approval calendar. Use it for X operations: tweet search, exports, monitors, webhooks, SDK jobs, and connected-account writes. | | Reply to comments and track engagement | Buffer Community covers comment replies, notifications, filters, saved replies, AI replies, comment score, and turning comments into posts across supported channels. | Xquik is not a social inbox. Use it when comments, replies, or mentions need API records, CSV/JSON/XLSX exports, monitor events, or signed webhook delivery. | | Search tweets, export followers, or monitor X accounts | Not a Buffer core task; Buffer centers on publishing, analytics, and community management. | Search tweets and follower exports cost 1 credit/result. Active monitors check every 1 second and cost 21 credits per monitor-hour. | Choose Buffer when scheduling and approvals across channels matter most. Choose Xquik when X is the main channel and the workflow needs raw records, export files, signed webhooks, REST endpoints, SDKs, MCP, or DMs. ## Xquik value to test For social scheduling comparisons, test what happens before and after the post: find source tweets, upload media, post tweets, send DMs, export replies, monitor keywords, and notify downstream tools. Choose Xquik when publishing must include API records, export files, monitor events, or signed webhooks. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: schedule an X thread, reply to comments, export followers, upload media, send a DM, search tweets for a campaign term, monitor an account, or deliver a webhook. Compare draft flow, queue controls, approval state, tweet IDs, author IDs, timestamps, post results, CSV/JSON/XLSX exports, webhook signatures, and error handling. Compare Buffer channels, scheduled-post volume, Community replies, analytics reports, approval workflows, and integrations with Xquik credits, active monitor billing, top-ups, and engineering time. ## Migration path Do not move the whole social calendar first. Keep Buffer for multi-channel planning, queues, threaded posts, comment replies, analytics, and approval workflows if that is already the operating model. Move one X-only task when the output must become API data, files, signed webhooks, or MCP tool results. Keep the test small: one task, one output, one cost model, and one downstream owner. Use Xquik for the X work that needs tweet search, follower export, post actions, media uploads, DMs, monitor tweets, webhook delivery, SDK calls, or MCP. Compare Buffer and Xquik by channel count, scheduled-post volume, approval needs, tweet, profile, follower, reply & timeline needs, export format, webhooks, and API handoff. Price the real workload. On Buffer, price connected channels, scheduled-post volume, analytics, community inbox, and approvals. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current channel pricing, post limits, and plan features before making a final decision. Verify current supported channels, queue, calendar, threaded-post, and first-comment scheduling features. Verify current comment reply, notification, filter, saved reply, AI reply, and comment score features. Verify current Free, Essentials, Team, and fair-use scheduled-post limits. Verify current comment sorting, liking, notifications, and reply behavior. # ChirrApp Alternative for Tweet Threads | Tweet API Source: https://docs.xquik.com/alternatives/chirrapp Compare ChirrApp with Xquik for tweet threads, tweet search, follower exports, post tweets and replies, monitors, signed webhooks, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether ChirrApp or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current ChirrApp pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team needs a focused editor for writing, splitting, previewing, scheduling, and cross-posting Twitter/X threads. You need publishing plus tweet search, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, and MCP in the same account. ## Source-backed ChirrApp scope ChirrApp's official home says it helps teams write and schedule Twitter threads in a distraction-free editor. The visible app navigation includes drafts, schedule, history, analytics, reply to a tweet, numbering, media upload, split text, tips, and focus controls. The home page says writers can repurpose blog content with auto-split, save and share drafts of tweets and threads, add images, videos, and GIFs, cross-post to LinkedIn and Mastodon, connect multiple accounts, and schedule tweets or Twitter threads in advance. The about page describes ChirrApp as a tool for experts and teams to write and schedule Twitter threads. It says writers can type a full message, split it into 280-character tweets, preview the thread before publishing, import article text with the browser extension, autosave drafts, and schedule threads. The scheduling guide says ChirrApp's editor can schedule at a specific date and time, add content to a fixed queue, share next, or pick a scheduled slot. It also says queues default to 9 am, noon, and 4 pm and can be adjusted by day. The thread-scheduling guide says the editor can preview the whole thread, split text into new tweets, add up to 4 images to each tweet, add a GIF or video, quote tweets, add emojis, and automatically number new tweets in a thread. The same guide says published threads can be cross-posted to LinkedIn, but LinkedIn scheduling is not currently supported there. It also says ChirrApp offers LinkedIn cross-posts but not Instagram or Facebook cross-posts. ChirrApp's team-focused guide says analytics can show a heatmap of when content gets engagement, schedule content against that timing, resurface tweets or threads for different time zones, show scheduled content in queue/week/month views, organize drafts with stars and folders, and loop saved tweets or threads through an evergreen content pool. The rendered official pricing surface does not expose stable plan prices in public text. Validate ChirrApp paid scheduling, team, account, analytics, and API availability at checkout before buying. ## Current cost checkpoint Use ChirrApp units when the job is focused thread writing and scheduling. Use Xquik units when the job needs X search, follower exports, write actions, media upload, DMs, monitors, signed webhooks, SDKs, exports, or MCP. | Task | ChirrApp unit to price | Xquik unit to price | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Write and schedule a thread | Price by scheduling access, thread length, media limits, draft workflow, and connected accounts. | Use Xquik compose and write actions when thread publishing also needs account actions, media upload, DMs, or API automation. | | Turn an article into a thread | Price ChirrApp browser-extension import, auto-split, preview, draft sharing, and scheduling. | Use Xquik when the workflow also needs tweet search, source tweet collection, reply export, or downstream API handoff. | | Plan a content calendar | Price queue, scheduled slot, weekly/monthly calendar, analytics heatmap, and evergreen pool needs. | Xquik monitors and events support stored records, signed webhooks, REST polling, and exports after publishing. | | Cross-post the result | Public ChirrApp docs describe LinkedIn and Mastodon cross-posting; one guide says no LinkedIn scheduling and no Instagram or Facebook cross-posting. Validate current support before buying. | Use Xquik when X is the system of record and other tools consume CSV, JSON, XLSX, webhook, SDK, or MCP outputs. | | Search tweets or export followers | ChirrApp's public scope centers on thread writing, scheduling, drafts, analytics, and cross-posting. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | ## Comparison | Area | ChirrApp | Xquik | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Writers and teams need a purpose-built Twitter/X thread editor, scheduler, calendar, and cross-posting flow. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Thread writing, preview, scheduling, analytics, and cross-posting product. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Thread editor, text splitting, preview, drafts, media attachments, scheduling, queues, calendar views, analytics heatmap, evergreen content pool, LinkedIn and Mastodon cross-posting. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Public rendered pages require checkout validation for current paid scheduling, team, account, analytics, and API terms. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for editor import, auto-split review, thread preview, scheduling, calendar management, cross-post support, and analytics review. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | ChirrApp fits thread authoring and scheduling when the output is the published thread. | Xquik is broader: compose posts, run account actions, extract data, and automate follow-up tasks. | | Primary use | Long-form-to-thread publishing and calendar planning. | X tools for compose, extraction, monitoring, and export. | | Developer tools | Writer-focused publishing workflow. | REST API, webhooks, and MCP server. | | Operational use | Drafts, queues, analytics, evergreen resurfacing, and cross-posting. | Creator, developer, growth, and support tasks. | ## Operating model Compare ChirrApp and Xquik by output, cost, and handoff. ChirrApp fits thread authoring and scheduling when the output is the published thread. Xquik is broader: compose posts, run account actions, extract data, and automate follow-up tasks. Primary use: Long-form-to-thread publishing and calendar planning. Xquik: X tools for compose, extraction, monitoring, and export. Developer tools: Writer-focused publishing workflow. Xquik: REST API, webhooks, and MCP server. Operational use: Drafts, queues, analytics, evergreen resurfacing, and cross-posting. Xquik: Creator, developer, growth, and support tasks. ## Xquik value to test For publishing comparisons, test what happens before and after the post: find source tweets, upload media, send DMs, export replies, monitor keywords, and notify downstream tools. Choose Xquik when publishing must include that data loop. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm draft flow, media handling, scheduling needs, export formats, API access, and whether data or monitoring matters after publishing. Then compare seats, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one publishing or reporting task. Keep the content calendar stable while you validate exports, API access, monitoring, and alerts in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Review ChirrApp public pricing and Xquik pricing for current terms. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. # Hootsuite Alternative for X Monitoring | Monitor API Source: https://docs.xquik.com/alternatives/hootsuite Compare Hootsuite with Xquik for social media management, tweet search, follower exports, post tweets, monitor tweets, webhooks, SDKs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Hootsuite or Xquik fits the X part of a social media management workflow: schedule posts, review inboxes, analyze campaigns, search tweets, export followers, monitor accounts or keywords, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Hootsuite pricing, plan limits, listening access, inbox features, analytics exports, and product terms on the official site before buying. ## Quick answer You need a cross-channel social media management suite for publishing, approvals, inboxes, analytics, social listening, ads, integrations, and team reporting. You need the X part in code: tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, or MCP. ## Source-backed Hootsuite scope Hootsuite's official plans page lists Standard with up to 10 social accounts, unlimited post scheduling, recommended posting times, AI image and caption generation, Canva and Adobe Express templates, one inbox, DM automations, 7-day brand and competitor mention search, sentiment analysis, 5 competitor benchmarks, and DM assignments when multiple users are present. Hootsuite's Advanced plan adds unlimited social accounts, customizable analytics reports and templates, saved message replies, automated responses, bulk scheduling for up to 350 posts, automatic message routing and tagging, 20 competitor benchmarks, report export, email, and scheduling, 30-day brand and competitor mention search, and outbound post tagging. The plans page also describes analytics report exports as PDF, PPT, CSV, XLSX, and scheduled email, plus a social content calendar export as CSV or PDF. Hootsuite's publishing and Enterprise pages describe list and calendar views for paid, organic, published, and scheduled posts; custom approval workflows; task assignment; posting-time recommendations based on audience data; AI-generated posts and images; Whiteboard planning; universal inboxes for comments and DMs; saved and automated replies; automated routing; agent collision avoidance; customer satisfaction surveys; social listening for trends, topics, mentions, hashtags, sentiment, and millions of websites; SSO; advanced analytics; advanced inbox; employee advocacy; review management; compliance integration; chatbot; and Salesforce integration. Hootsuite's official plans page lists Standard at USD 99 per user/month billed annually with 10 social accounts, Advanced at USD 249 per user/month billed annually with no listed social-account cap, and Enterprise as contact pricing with a customized plan, customer support, employee advocacy, premium listening, advanced analytics, advanced inbox, custom user access permissions, Salesforce integration, and services. The plans page also lists a free 30-day trial and a skip-trial discount on Standard and Advanced. ## Current cost checkpoint Use this checkpoint before comparing Hootsuite with Xquik: | Job | Hootsuite pricing signal | Xquik pricing signal | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Manage a small social workspace | Standard starts at USD 99 per user/month billed annually with 10 social accounts. | Starter is USD 20/month with 140,000 credits for X tasks and API access. | | Manage more social accounts and reports | Advanced starts at USD 249 per user/month billed annually and has no listed social-account cap. | Xquik pricing is based on credits, active monitors, and connected X work, not social account seats. | | Add enterprise governance | Enterprise uses contact pricing with a customized plan, custom user access permissions, support, and services. | Xquik stays focused on tweet and profile records, follower exports, monitor events & webhooks, account actions, monitors, webhooks, SDKs, MCP, and exports. | | Add inbox, analytics, and listening workflows | Advanced and Enterprise are the pricing checkpoints for deeper reporting, inbox routing, scheduled exports, and longer mention search windows. | Tweet search, follower exports, monitor events, CSV/JSON/XLSX files, and webhook delivery use the Xquik credit model. | ## Comparison | Area | Hootsuite | Xquik | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Marketing, support, and enterprise social teams need one workspace for multi-network publishing, approvals, inboxes, analytics, listening, ads, and integrations. | Teams need tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Social media management, publishing, engagement, listening, analytics, ads, and enterprise collaboration suite. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Hootsuite's official pages describe social scheduling, AI content help, recommended posting times, bulk scheduling, calendar and list views, approval workflows, inboxes, saved replies, automated responses, automated routing, analytics exports, competitor benchmarks, listening, SSO, advanced inbox, compliance integration, chatbot, and Salesforce integration. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Check seats, social accounts, analytics exports, report scheduling, inbox access, listening access, approval workflows, SSO, compliance, and Enterprise add-ons. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for suite setup, channels, approval flows, inbox routing, reports, listening topics, CRM or team integrations, and downstream export ownership. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Hootsuite covers cross-channel planning, publishing, inboxes, analytics, listening, ads, approvals, and enterprise controls. | Xquik is focused on X records, account actions, monitors, API access, signed webhooks, and export files. | | Buying motion | Social suite procurement, user permissions, approvals, and reporting ownership. | Self-serve tweet, profile, follower, reply & account actions with API access. | | Team process | Planning, approvals, inbox routing, analytics, listening topics, and ads reporting. | X extraction, post actions, media uploads, DMs, monitoring, and webhooks. | | Implementation | Team process, social account setup, report templates, workflows, and integrations. | Dashboard setup plus API keys, webhook signing, SDK calls, exports, and MCP. | ## Operating model Compare Hootsuite and Xquik by output, cost, and handoff. Hootsuite covers cross-channel planning, publishing, inboxes, analytics, listening, ads, approvals, and enterprise controls. Xquik is focused on X records, account actions, monitors, API access, signed webhooks, and export files. Hootsuite: social suite procurement, permissions, approvals, and reporting ownership. Xquik: self-serve tweet, profile, follower, reply & account actions with API access. Hootsuite: channels, approval flows, inbox routing, reports, listening topics, and integrations. Xquik: API keys, webhooks, SDKs, exports, and MCP. Hootsuite: reports, inbox work, campaign views, and social listening alerts. Xquik: Tweet JSON, follower CSV/XLSX, monitor events, and webhook payloads. ## Xquik value to test For social media management comparisons, test what happens before and after the post: find source tweets, upload media, send DMs, export replies, monitor keywords, and notify downstream tools. Choose Xquik when the X workflow must produce API responses, files, or signed events instead of staying inside a social suite. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: schedule a campaign post, search tweets for a product term, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare approval needs, inbox routing, analytics exports, tweet IDs, author IDs, timestamps, post results, CSV/JSON/XLSX exports, webhook signatures, and error handling. Compare Hootsuite seats, social accounts, analytics, approvals, listening, and Enterprise add-ons with Xquik credits, active monitor billing, top-ups, and engineering time. ## Migration path Do not move the entire social stack first. Keep Hootsuite for cross-channel planning, inboxes, approvals, analytics, ads, and listening if that is already the operating model. Move one X-only task when the output must become API data, files, signed webhooks, or MCP tool results. Keep the test small: one task, one output, one cost model, and one downstream owner. Use Xquik for the X work that needs tweet search, follower export, post actions, media uploads, DMs, monitor tweets, webhook delivery, SDK calls, or MCP. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current publishing, engagement, listening, analytics, ads, and integration features. Verify current plan limits, analytics exports, inbox access, listening access, and add-ons. Verify current calendar, approval, AI writing, best-time, bulk scheduling, and planning features. Verify current SSO, advanced analytics, advanced inbox, listening, advocacy, chatbot, compliance, and Salesforce options. # Hypefury Alternative for Tweet Publishing APIs Source: https://docs.xquik.com/alternatives/hypefury Compare Hypefury with Xquik for tweet publishing, tweet search, follower exports, replies, monitors, signed webhooks, REST APIs, SDKs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Hypefury or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Hypefury pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team mainly needs creator scheduling, recurring posts, Auto-DM campaigns, autoplugs, analytics, and cross-posting. You need publishing plus tweet search, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, and MCP in the same account. ## Source-backed Hypefury scope Hypefury's official pricing page lists Starter, Creator, Business, and Agency plans with 7-day trials. Starter is USD 29/month and includes scheduling up to 1 month, 6 social accounts with 1 X account, long posts, autoplugs, automatic cross-posting, viral thread hooks, tweet templates, 1 Auto-DM tweet per week with a 100 DMs/day limit, engagement builder access for 30 users and 5 keywords, automated Gumroad sales, and email plus chat support. Creator is USD 65/month and includes scheduling up to 3 months, 30 social accounts with 5 X accounts, 250 Auto-DMs/day, 10 Tweet-to-Reels automations/month, engagement builder access for 100 users and 10 keywords, and weekend support. Business is USD 97/month with 60 social accounts, 10 X accounts, 300 Auto-DMs/day, 50 Tweet-to-Reels automations/month, and engagement builder access for 20 keywords. Agency is USD 199/month with 90 social accounts, 15 X accounts, 400 Auto-DMs/day, 300 Tweet-to-Reels automations/month, and engagement builder access for 50 keywords. The same official page says Hypefury no longer offers a free plan, but offers a 7-day trial on Starter. It lists Instagram, Facebook Pages, LinkedIn, Threads, and TikTok in the plan comparison, and says Hypefury supports Instagram business accounts, LinkedIn posts and carousels, Threads, and X/Twitter. The official publish page describes creating content once, scheduling it, and distributing it to other social channels. It lists a minimal editor, inspiration from 15+ hand-curated niches, 30+ tweet templates, and recurrent posting plans. The official Auto DMs help page says DMs run in batches every 30 minutes, campaigns last 3 days, the number of DMs/day depends on plan, edited tweets can break Auto DM because the tweet ID changes, and DM recipients must follow the account. ## Comparison | Area | Hypefury | Xquik | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Creators want X-first content scheduling, recurring posts, Auto-DM campaigns, autoplugs, engagement builder, analytics, and cross-posting. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Creator publishing and social automation dashboard. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Official pricing lists scheduling, long posts, autoplugs, automatic cross-posting, viral hooks and templates, Auto-DMs, watched users, watched keywords, Gumroad sales, Tweet-to-Reels, analytics, and social account limits by plan. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Official pricing lists Starter at USD 29/month, Creator at USD 65/month, Business at USD 97/month, and Agency at USD 199/month, each with a 7-day trial. Plan value depends on social accounts, X accounts, scheduling horizon, Auto-DM limits, engagement builder limits, and cross-posting needs. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for handoffs when content needs tweet search, follower export, monitor events, webhook delivery, SDK calls, or MCP beyond the publishing dashboard. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Hypefury fits creator scheduling, cross-posting, Auto-DM campaigns, recurring posts, and creator analytics. | Xquik fits teams that also need tweet search, follower exports, account monitors, signed webhooks, SDKs, and API calls. | | Primary use | Creator publishing, recurring posts, and cross-posting. | Tweet, follower, reply, profile, post, monitor & export workflows. | | Automation depth | Publishing dashboard automation, Auto-DMs, engagement builder lists, and cross-posting. | REST endpoints, webhooks, MCP, and dashboard actions. | | Team fit | Creators and small content teams. | Creators, developers, growth teams, and ops teams. | ## Operating model Compare Hypefury and Xquik by output, cost, and handoff. Hypefury fits creator publishing, Auto-DM campaigns, engagement builder work, and cross-posting. Xquik fits teams that also need tweet search, follower exports, account monitors, signed webhooks, SDKs, and API calls. Primary use: Creator publishing and queue management. Xquik: Tweet, follower, reply, profile, post, monitor & export workflows. Automation depth: Publishing oriented automation. Xquik: REST endpoints, webhooks, MCP, and dashboard actions. Team fit: Creators and small content teams. Xquik: Creators, developers, growth teams, and ops teams. ## Current cost checkpoint Use current Hypefury plan limits when the job is creator publishing. Use Xquik units when the job needs tweet search, follower exports, media posts & account monitors, exports, monitors, DMs, media uploads, webhooks, SDKs, or MCP. | Task | Hypefury unit to price | Xquik unit to price | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Schedule creator posts | Starter is USD 29/month for scheduling up to 1 month, 6 social accounts, and 1 X account. Creator is USD 65/month for scheduling up to 3 months, 30 social accounts, and 5 X accounts. | Compose, refine, and score are free. Creating a tweet or reply costs 30 credits/call from a connected X account. | | Run Auto-DM campaigns | Starter lists 1 Auto-DM tweet per week with a 100 DMs/day limit. Creator, Business, and Agency list 250, 300, and 400 Auto-DMs/day. Hypefury says Auto-DM batches run every 30 minutes and campaigns last 3 days. | Send DM costs 10 credits/call. Use Xquik when the same workflow needs media upload, follower export, webhook delivery, SDK calls, or event storage. | | Search tweets or export followers | Validate this during trial; Hypefury's current public pricing is organized around creator publishing, engagement builder, social accounts, and Auto-DM limits. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | | Monitor accounts or keywords | Engagement builder tracks watched users and keywords inside the creator workflow. | Active monitors check every 1 second and cost 21 credits per monitor-hour. Stored events and signed webhook delivery management are included. | | Connect backend jobs or agents | Validate REST, SDK, webhook, and MCP handoff needs before buying. | REST API, signed webhooks, 10 SDKs, MCP, and CSV/JSON/XLSX/Markdown exports. | ## Xquik value to test For publishing comparisons, test what happens before and after the post: find source tweets, upload media, send DMs, export replies, monitor keywords, and notify downstream tools. Choose Xquik when publishing must include that data loop. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm draft flow, media handling, scheduling needs, export formats, API access, and whether data or monitoring matters after publishing. Then compare seats, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Hypefury Trial Evidence Test scheduled publishing and post-publication research. Use the same account, draft, media, and review window. For Hypefury, record the draft, schedule, approval, post, and queue failures. For Xquik, record media uploads, write receipts, Tweet IDs, and replies. Keep followers and monitor events. Do not score a publishing calendar by API breadth. Do not score an API by calendar features. The tools may work together. Keep Hypefury for planning. Keep Xquik for traceable X operations. ## Migration path Start with one publishing or reporting task. Keep the content calendar stable while you validate exports, API access, monitoring, and alerts in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Check live Hypefury pricing and Xquik pricing because public plans change. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current plan limits, Auto-DM limits, scheduling horizons, and supported social channels before making a final decision. # Late / Zernio Alternative for Social Scheduling Source: https://docs.xquik.com/alternatives/late Compare Zernio, formerly Late, for social scheduling and cross-posting with Xquik tweet search, follower exports, monitors, signed webhooks, SDKs, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Zernio, formerly Late, or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Zernio pricing, access rules, and product terms on the official site before buying. ## Quick answer You need one API for posting, analytics, comments, messaging, or ads across many social networks. You need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, or MCP from one product. ## Source-backed Zernio scope Zernio's official rebrand page says Late is now Zernio, with the same API, team, webhooks, `/api/v1` endpoints, route parameters, and response schemas. It says `getlate.dev` redirects to `zernio.com`, old SDK packages keep working during the grace period, and integrations do not require code changes immediately. Zernio's official docs describe a social media scheduling API for managing and publishing content across major platforms from `https://zernio.com/api/v1`, with Bearer authentication, profile creation, scheduled posts, immediate posts, and cross-posting to multiple accounts. Zernio's pricing docs describe pay-per-connected-account billing: the first 2 connected accounts are free, accounts 3-10 cost USD 6/account/month, accounts 11-100 cost USD 3/account/month, accounts 101-2,000 cost USD 1/account/month, and more than 2,000 accounts require custom pricing. Zernio's May 2026 pricing post says analytics, comments, DMs, ads, webhooks, MCP server, CLI, SDKs, dashboard, and team members are included per connected account. It also says X/Twitter usage is passed through separately at USD 0.005/read, USD 0.010/write, and USD 0.015/DM. Zernio's platform docs list Twitter/X limitations for DMs and cached reply search, plus API reference areas for account connection, post scheduling, media upload, analytics, ads, messages, comments, reviews, webhooks, and account settings. ## Comparison | Area | Zernio | Xquik | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Developers choosing one social API across many channels. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Multi-platform social API. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Zernio docs describe publishing, scheduling, cross-posting, analytics, inbox work, ads, webhooks, MCP, CLI, SDKs, and dashboard workflows across connected social accounts. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Zernio pricing is per connected account: first 2 accounts free, then graduated monthly account rates. Zernio says X/Twitter usage is passed through separately at USD 0.005/read, USD 0.010/write, and USD 0.015/DM. Verify live rates before buying. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for handoffs when content needs data, exports, monitoring, alerts, or automation beyond the calendar. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Zernio provides a unified social media API for multi-platform posting and engagement. | Xquik goes deeper on X: tweet search, follower exports, media uploads, DMs, monitors, event logs, signed webhooks, SDKs, and MCP. | | Channel scope | One API for many social networks. | One focused platform for tweet search, profile lookup, follower exports, reply scraping & account actions. | | Use case | Scheduling, publishing, analytics, comments, messaging, and ads. | Search, followers, extractions, monitors, events, and draws. | | Data exports | Publishing and social management responses. | Portable exports, event logs, webhook payloads, and API data. | ## Operating model Compare Zernio and Xquik by output, cost, and handoff. Zernio provides one API across many social networks. Xquik goes deeper on X: tweet search, follower exports, media uploads, DMs, monitors, event logs, signed webhooks, SDKs, and MCP. Channel scope: One API for many social networks. Xquik: One focused platform for tweet search, profile lookup, follower exports, reply scraping & account actions. Use case: Scheduling, publishing, analytics, comments, messaging, and ads. Xquik: Search, followers, extractions, monitors, events, and draws. Data exports: Publishing and social management responses. Xquik: Portable exports, event logs, webhook payloads, and API data. ## Xquik value to test For publishing comparisons, test what happens before and after the post: find source tweets, upload media, send DMs, export replies, monitor keywords, and notify downstream tools. Choose Xquik when publishing must include that data loop. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm draft flow, media handling, scheduling needs, export formats, API access, and whether data or monitoring matters after publishing. Then compare seats, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one publishing or reporting task. Keep the content calendar stable while you validate exports, API access, monitoring, and alerts in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Compare Zernio and Xquik by connected-account count and X usage charges. Test tweet search, profile lookup, follower and reply exports, timeline depth, and webhook delivery. Use the X usage line item as the first cost test. Zernio's [May 2026 pricing post](https://zernio.com/blog/pay-per-account-pricing) lists X reads at $0.005/read, writes at $0.010/write, and DMs at $0.015/DM. On Xquik, many X reads use 1 credit, common non-tweet write or DM actions use 10 credits, and tweet or reply posts use 30 credits before media surcharges. At top-up pricing, that is $0.00015 for a 1-credit read, $0.0015 for a 10-credit write or DM, and $0.0045 for a 30-credit tweet or reply before included monthly credits. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current connected-account and X usage pricing before making a final decision. # Make Alternative for X Automation | API Comparison Source: https://docs.xquik.com/alternatives/make Compare Make with Xquik for X/Twitter automation, tweet search, follower exports, monitors, webhooks, APIs, MCP, and scenarios. Compare costs and API coverage.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Make, Xquik, or both fit a scenario that needs X/Twitter data, account actions, alerts, exports, webhooks, API calls, or downstream app automation. This is a factual comparison and migration guide. Verify current Make plans, credit rules, HTTP app behavior, custom app options, and platform terms on official Make pages before buying. ## Quick answer You need a visual automation builder for many apps, scheduled scenarios, routers, data transforms, approvals, and destination workflows. You need focused X API tasks: tweet search, follower exports, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, MCP, and credit-priced API calls. Make should orchestrate the scenario while Xquik supplies tweet search results, follower exports, account actions & monitor events, write actions, monitor events, webhook payloads, exports, or API calls. ## Source-backed Make scope Make's official pricing page says each module action in a scenario counts as one credit, Free includes 1,000 credits/month, and paid plans add more schedule control, data transfer, execution, team, and enterprise features. Make's official pricing table also lists the active scenario cap, minimum scheduled-run interval, maximum scenario execution time, file size, execution log storage, and Make API endpoint limits by plan. Make's official HTTP app documentation says the HTTP app can call services without a native Make integration, use authenticated or unauthenticated HTTPS requests, parse responses, upload or download files, and configure offset, page, URL/link, or cursor-style pagination. Make's official webhook and custom app docs describe instant webhooks, custom webhooks, webhook queues, response handling, and custom app module types for actions, searches, polling triggers, instant webhook triggers, universal calls, and responders. ## Comparison | Area | Make | Xquik | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Teams need a visual scenario builder for app-to-app automation, schedules, routers, transformations, and operational handoffs. | Teams need X/Twitter records, account actions, monitor events, exports, signed webhooks, SDKs, and MCP from one X-focused platform. | | Product type | Visual automation platform with apps, scenarios, HTTP requests, webhooks, custom apps, and AI automation features. | Tweet search results, follower exports, account actions & monitor events, account actions, monitors, webhooks, exports, dashboard tools, REST API, SDKs & MCP. | | X/Twitter path | Use Make's HTTP app, webhook app, or a private custom app to call a focused X service, then route the result through Make modules. | Start with a dashboard tool, REST endpoint, SDK call, export, webhook subscription, or MCP tool for a defined X task. | | Returned data | Scenario bundles, module outputs, files, variables, and downstream app payloads shaped by the scenario. | API responses, CSV/JSON/XLSX exports, monitor events, webhook payloads, action logs, and MCP responses. | | Cost model | Make pricing is based on credits, plan features, active scenarios, schedule intervals, execution time, file sizes, log storage, and API endpoint limits. | Starter is USD 20/month with 140,000 included credits. Top-ups are USD 0.00015/credit, webhook management is free, and active monitors bill while enabled. | | API fit | Make is the orchestration layer. Its HTTP app can call APIs and handle pagination, but you still need a reliable tweet, profile, follower, reply & timeline API contract. | Xquik is the X API layer. It handles X-specific endpoints, pagination, exports, account actions, monitors, signed webhooks, SDKs, and MCP. | | Summary | Make is useful when the main problem is moving data across many tools with visual scenario logic. | Xquik is useful when the main problem is reliable tweet search results, follower exports, account actions & monitor events, X account actions, monitoring, webhooks, exports, and agent handoff. | ## Best combined scenario For X/Twitter automation, Make and Xquik usually fit together. Let Make own schedules, filters, routers, approvals, and destination apps. Let Xquik own tweet search, user lookups, follower exports, media uploads, DMs, monitor events, signed webhooks, and API response contracts. Make: build a scenario with HTTP, webhook, and destination modules. Xquik: create one API key for the X task. Make: route scenario bundles to apps. Xquik: return tweet records, user records, exports, monitor events, and webhook payloads. Make: send results to CRMs, Slack, Sheets, Airtable, queues, or databases. Xquik: supply REST, signed webhooks, SDKs, exports, and MCP. ## Xquik scenarios to run from Make Use Xquik inside Make when the scenario needs a concrete X-specific API step before routing to other apps. Call `GET /x/tweets/search`, filter records in Make, then add rows to Sheets, Airtable, a CRM, or a warehouse. Create an extraction job, poll until `completed`, then upsert follower, following, list, community, or Space rows. Receive signed Xquik webhook payloads for account or keyword monitors, then route alerts by event type, author, text, or engagement. Use Make approvals or content sources, then call Xquik to create tweets, upload media, send DMs, or run other account actions. ## Monitor webhook receiver handoff When a Make webhook receives Xquik monitor events, verify `X-Xquik-Signature` before routing bundles to routers, Slack, Sheets, queues, or CRMs. Store `deliveryId` and `streamEventId` as separate Make data-store keys: use `deliveryId` for endpoint retry de-dupe and `streamEventId` when one monitor event should process once across scenario changes. Return `2xx` after accepting a duplicate `deliveryId` or `streamEventId`; later modules can skip the already-processed bundle. Keep shared scenario rows to `deliveryId`, `streamEventId`, `eventType`, `occurredAt`, `username` or `query`, and mapped tweet fields. Do not store endpoint signing values, raw request body, raw signature, or full headers in scenario logs, data stores, Slack messages, CRM rows, or retry queues. ## Trial checklist Use one task: tweet search, follower export, monitor alert, media upload, direct message, or account action. Add the trigger, Xquik HTTP or custom-app module, transform step, error route, and destination app. Confirm returned fields, pagination, `Retry-After` handling, webhook signature verification, export format, and downstream mapping. Compare Make credits, active scenarios, schedule interval, Xquik credits, active monitor billing, and engineering time for retries and alerts. ## Migration path Do not rebuild the whole Make scenario first. Replace only the brittle X/Twitter step. 1. Keep the Make trigger, router, transform, approval, and destination modules. 2. Replace a custom X step or manual export with a Xquik REST call, extraction, webhook, or private custom app module. 3. Map Xquik fields into the existing Make bundle structure. 4. Add explicit routes for `401`, `402`, `429`, and `5xx` responses. ## Official sources to verify Verify current credits, active scenarios, schedule intervals, file limits, and plan features. Verify HTTP request behavior, authentication options, pagination, and request modules. Verify the current app directory, HTTP integration, and automation capabilities. Verify instant webhook triggers, custom webhook URLs, queues, response handling, and webhook rate behavior. Verify action, search, polling trigger, instant trigger, universal, and responder module types. Build a private Make custom app for Xquik search, trends, extractions, monitor webhooks, and X actions. ## Xquik next steps Configure a private Make custom app for Xquik API-key auth, modules, webhooks, and polling triggers. Map tweet monitoring, signed webhooks, MCP agents, follower exports, and tweet composition to API calls. Deliver signed monitor events to Make webhooks, queues, CRMs, Slack, or databases. Check included credits, top-ups, free operations, and active monitor billing. # Meltwater Alternative for Tweet Monitoring Source: https://docs.xquik.com/alternatives/meltwater Compare Meltwater with Xquik for social listening, media monitoring, tweet search, follower exports, monitor tweets, signed webhooks, API access, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Meltwater or Xquik fits the X part of a media monitoring, social listening, or social media management workflow: search tweets, export followers, monitor accounts or keywords, send webhooks, and hand records to apps, warehouses, or agents. This is a factual comparison and migration guide. Verify current Meltwater pricing, access rules, and product terms on the official site before buying. ## Quick answer You need a broader media intelligence suite for social listening, media monitoring, social media management, influencer marketing, reporting, or PR workflows. You need the X part in code: tweet search, follower exports, 1-second monitors, signed webhooks, SDKs, MCP, and CSV/JSON/XLSX exports. ## Source-backed Meltwater scope Meltwater's official social monitoring pages describe coverage across major social networks, blogs, forums, podcasts, online news, reviews, owned channels, and public social content. Their public pages also describe X coverage, Boolean-style searches, sentiment views, share-of-voice and benchmarking metrics, dashboards, reports, real-time alerts, exportable charts, visual enrichments, influencer discovery, and a rolling social archive for analysis. Meltwater's official media monitoring and social media management pages describe online news, print, broadcast, podcast, and social monitoring, plus publishing, scheduling, asset management, social analytics, PR reporting, and owned-profile management in one suite. Meltwater's developer docs describe an Export API for exporting media articles and social mentions from existing searches, managing content exports, and accessing real-time listening data through Data Streams. Verify plan access, available fields, and export limits before committing to a workflow. Meltwater's current public pages point to a demo-led suite motion instead of published self-serve prices. The social monitoring page lists Instagram, TikTok, X, Facebook, Bluesky, LinkedIn, YouTube, and Reddit among covered channels, plus alerts through email, Slack, or other channels. The media monitoring page describes 400,000+ traditional media sources, 200M+ online publications, social sources such as Twitter, Facebook, Instagram, Reddit, and YouTube, 20,000+ podcasts, and broadcast. The suite page describes workflow integrations with Slack, Microsoft Teams, business intelligence platforms, APIs, and custom integrations. ## Current access checkpoint Use this checkpoint before comparing Meltwater with Xquik: | Job | Meltwater access signal | Xquik access signal | | ------------------------------------------ | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | Buy media intelligence or social listening | Plan for a demo-led suite evaluation, package scope, seats, onboarding, and reporting ownership. | Starter is USD 20/month with 140,000 credits for X tasks and API access. | | Monitor X alongside other sources | Confirm X/Twitter coverage, alert channels, export fields, package access, and freshness needs. | Xquik account and keyword monitors check active streams every 1 second and can deliver signed webhooks. | | Send data to teams or warehouses | Confirm Slack, Microsoft Teams, BI, Export API, Data Streams, and custom integration access. | Xquik returns tweet JSON, follower CSV/XLSX, monitor events, SDK responses, MCP results, and webhooks. | | Separate suite work from X jobs | Keep Meltwater for media monitoring, social listening dashboards, PR reporting, and suite workflows. | Use Xquik for tweet search, follower exports, monitor tweets, account actions, SDKs, MCP, and export handoffs. | ## Comparison | Area | Meltwater | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Communications, PR, marketing, or social teams need media monitoring, social listening, reporting, publishing, engagement, and broader team workflows. | Operators and developers need X records, account actions, monitor events, exports, webhooks, SDKs, and MCP without buying a broad media intelligence suite. | | Product type | Media intelligence, social listening, and social media management suite. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | X task coverage | Meltwater official pages describe X coverage, social listening, media monitoring, publishing, dashboards, reports, alerts, exports, and Export API access. Verify current X access, available fields, export limits, and package requirements before buying. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Output to test | Listening dashboards, reports, media monitoring results, social management workflows, alerts, and exports. | Tweet records, user records, follower rows, monitor events, signed webhook payloads, CSV/JSON/XLSX exports, SDK calls, and MCP tool results. | | Pricing & value | Check seat count, product suite scope, add-ons, exports, API access, onboarding, and implementation time. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for suite setup, monitored and owned connections, permissions, dashboards, reports, alerts, and downstream ownership. | Start from a dashboard task, then automate the same job through REST, webhook, SDK, export, or MCP when it needs to run repeatedly. | ## Operating model Compare Meltwater and Xquik by the job that has to leave the dashboard. Meltwater is useful when the team needs media monitoring and social listening across a broad communications workflow. Xquik is useful when the X work needs direct records, repeatable API calls, exports, signed webhooks, or agent access. Meltwater: media monitoring, social listening, reporting, publishing, and PR workflows. Xquik: Tweet search, follower exports, monitor tweets, and X API handoff. Meltwater: suite setup, seats, connections, dashboards, reports, and alerts. Xquik: API key, dashboard tools, REST calls, SDKs, webhooks, and MCP. Meltwater: reports, alerts, dashboards, and suite exports. Xquik: Tweet JSON, follower CSV/XLSX, webhook events, SDK responses, and MCP results. ## Xquik value to test For media monitoring and social listening comparisons, keep the trial focused on one X workflow. Compare returned records, export format, webhook delivery, API behavior, and cost. Start with tweet search, follower exports, monitor events, signed webhook payloads, CSV/JSON/XLSX exports, SDK calls, or MCP tool results. Call [Search Tweets](/api-reference/x/search-tweets) or run an extraction to collect tweets for a keyword, account, list, community, reply thread, quote chain, or mention query. Run follower exports through dashboard tools, extraction jobs, REST endpoints, or SDKs, then hand CSV, JSON, XLSX, Markdown, or paginated JSON to the next system. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. Use 128 REST operations, 10 SDKs, MCP, API keys, and pay-per-use read endpoints when the workflow needs code, agents, or repeatable jobs. ## What to verify in a trial Pick one real job: monitor a brand account, search tweets for a campaign term, export followers, or send new matching tweets to a webhook. Compare tweet IDs, author IDs, timestamps, text, metrics, media links, pagination, export fields, webhook signatures, and error handling. Compare Meltwater package requirements with Xquik credits, active monitor billing, top-ups, and the engineering time needed to keep the workflow running. ## Migration path Do not migrate the full media intelligence program first. Start with one X-only workflow and one downstream owner. 1. Use Meltwater for broad media monitoring, social listening dashboards, PR reporting, and social media management if that is already the operating model. 2. Use Xquik for the X task that needs API output: tweet search, follower export, monitor tweets, webhook delivery, SDK calls, or MCP. 3. Keep both systems side by side until the Xquik output matches the fields, freshness, and handoff format the downstream system needs. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Export tweet search results to CSV, JSON, XLSX, Markdown, or paginated JSON. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. Verify current news, social, print, broadcast, podcast, and monitoring coverage. Verify current publishing, analytics, asset, listening, and owned-profile workflow claims. Verify current export, search, content export, analysis request, and stream behavior. # n8n Alternative for X Automation | API Comparison Source: https://docs.xquik.com/alternatives/n8n Compare n8n with Xquik for X/Twitter automation, tweet search, follower exports, monitors, webhooks, APIs, MCP, and workflows. Compare costs and API coverage.
For the complete documentation index, see llms.txt.
Use this guide to decide whether n8n, Xquik, or both fit a workflow that needs X/Twitter data, account actions, alerts, exports, webhooks, AI agents, or downstream app automation. This is a factual comparison and migration guide. Verify current n8n plans, X node operations, self-hosting requirements, and platform terms on the official n8n pages before buying. ## Quick answer You need a broad workflow automation builder that connects many apps, runs scheduled workflows, and lets teams compose branching logic across systems. You need focused X API tasks: tweet search, follower exports, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, MCP, and credit-priced API calls. n8n should orchestrate the workflow while Xquik supplies tweet search results, follower exports, account actions & monitor events, write actions, monitor events, webhook payloads, exports, or MCP tools. ## Source-backed n8n scope n8n's official X node docs list built-in operations for direct messages, creating or replying to tweets, deleting tweets, searching tweets, liking tweets, retweeting tweets, getting users, and adding list members. n8n's official X node docs also say the X node can be used as an AI tool, with parameters set automatically or from AI-directed information. n8n's official HTTP Request node docs describe REST calls to any app or service with a REST API, generic credential methods, query parameters, headers, form, form-data, JSON, binary-file, and raw request bodies, response formatting, full response headers and status, batching, pagination, timeout, and cURL import. n8n's official executions docs say paid Cloud and self-hosted plans count production executions started automatically by triggers, schedules, or polling, while manual executions are excluded from quota counting. n8n's official Community Edition docs say the community edition includes almost the complete feature set, then lists features reserved for paid or registered editions such as projects, sharing, external credential storage, log streaming, multi-main mode, SSO, and Git version control. ## Comparison | Area | n8n | Xquik | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Teams need a general automation canvas for apps, approvals, schedules, AI steps, branching logic, and data movement. | Teams need X/Twitter records, account actions, monitor events, exports, signed webhooks, SDKs, and MCP from one X-focused platform. | | Product type | Workflow automation platform with Cloud and self-hosted options. | Tweet search results, follower exports, account actions & monitor events, account actions, monitors, webhooks, exports, dashboard tools, REST API, SDKs & MCP. | | X/Twitter path | Use the n8n X node for supported operations, or use HTTP Request nodes with an external tweet, profile, follower, reply & timeline API service. | Start with a dashboard tool, REST endpoint, SDK call, export, webhook subscription, or MCP tool for a defined X task. | | Returned data | Workflow items, node outputs, execution logs, and downstream app payloads shaped by the workflow. | API responses, CSV/JSON/XLSX exports, monitor events, webhook payloads, action logs, and MCP responses. | | Cost model | n8n pricing is based on workflow executions, plan features, hosting choice, and operational work for self-hosted deployments. | Starter is USD 20/month with 140,000 included credits. Top-ups are USD 0.00015/credit, webhook management is free, and active monitors bill only while enabled. | | API fit | n8n is the orchestrator. It can call APIs, transform records, and route outputs, but it does not replace a focused tweet, profile, follower, reply & timeline API contract. | Xquik is the X API layer. It handles X-specific endpoints, pagination, exports, account actions, monitors, signed webhooks, and MCP. | | Summary | n8n is useful when the main problem is connecting many systems into one workflow. | Xquik is useful when the main problem is reliable tweet search results, follower exports, account actions & monitor events, X account actions, monitoring, webhooks, exports, and agent handoff. | ## Best combined workflow For many teams, n8n and Xquik should sit together. Let n8n own orchestration, approvals, schedules, and destination apps. Let Xquik own tweet search results, follower exports, account actions & monitor events, write actions, exports, event delivery, and MCP. n8n: create workflow triggers and destination app credentials. Xquik: create one API key or MCP credential for X tasks. n8n: route transformed workflow items. Xquik: return tweet records, user records, exports, monitor events, and webhook payloads. n8n: send results to CRMs, Slack, Sheets, queues, or databases. Xquik: supply REST, signed webhooks, SDKs, exports, and MCP tools. ## Xquik workflows to run from n8n Use Xquik inside n8n when the workflow needs a dependable X-specific data or action step before routing to other apps. Call `GET /x/tweets/search`, filter by engagement or author, then send matching records to a CRM, spreadsheet, queue, or Slack channel. Create an extraction job, poll until `completed`, then append follower, following, list, community, or Space rows to Google Sheets. Receive signed Xquik webhook payloads for account or keyword monitors, then branch by event type, author, text, or engagement fields. Connect n8n AI Agent workflows to the Xquik MCP server for tweet search, user lookups, trends, extraction planning, and source links. ## Trial checklist Use one task: tweet search, follower export, monitor alert, media upload, DM send, or AI research. Build the n8n trigger, Xquik HTTP Request or MCP step, data transform, and destination app step. Confirm returned fields, pagination, `Retry-After` handling, webhook signature verification, export format, and downstream mapping. Compare n8n workflow executions, hosting or plan needs, Xquik credits, active monitor billing, and engineering time for retries and alerts. ## Migration path Do not replace n8n if it already orchestrates the business workflow. Replace only the brittle X/Twitter step. 1. Keep the n8n workflow trigger and destination apps. 2. Replace a custom X node, unofficial scraper step, or manual export with a Xquik REST call, extraction, webhook, or MCP tool. 3. Map Xquik fields into the existing n8n item shape. 4. Add explicit handling for `401`, `402`, `429`, and `5xx` responses. ## Official sources to verify Verify current X node operations, credentials, and supported actions. Verify n8n Cloud, self-hosting guidance, licensing, and platform choice. Verify workflow execution pricing, plan features, Cloud options, and self-hosted options. Verify REST calls, credentials, headers, bodies, response formatting, pagination, batching, timeouts, and cURL import. Verify production execution quota behavior, manual executions, workflow-level execution lists, and all-execution lists. Verify Community Edition features, registered features, and paid-edition feature boundaries. Build concrete Xquik recipes for n8n HTTP Request nodes, webhooks, extraction jobs, and MCP. ## Xquik next steps Configure API-key auth, monitor webhooks, extraction jobs, and MCP tools in n8n. Map tweet monitoring, signed webhooks, MCP agents, follower exports, and tweet composition to API calls. Deliver signed monitor events to n8n Webhook Trigger nodes or downstream queues. Check included credits, top-ups, free operations, and active monitor billing. # Outstand Alternative for Tweet Publishing APIs Source: https://docs.xquik.com/alternatives/outstand Compare Outstand with Xquik for tweet publishing, tweet search, follower exports, replies, monitors, signed webhooks, REST APIs, SDKs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Outstand or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Outstand pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team needs a cross-platform publishing API with pay-per-post pricing, MCP tools, media upload, analytics, and webhooks. You need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, or MCP from one product. ## Source-backed Outstand scope Outstand's official home describes it as a unified social media API for builders with 10 platforms, 1 API, and pay-per-post pricing. The home page says one API call can post to X, LinkedIn, Instagram, and 7 other platforms. It lists X, LinkedIn, Instagram, TikTok, Facebook, Threads, Bluesky, YouTube, Pinterest, and Google Business. Current pricing on the home page lists USD 5/month with 1,000 included posts and USD 0.01 per post over 1,000. It also lists connected accounts without a visible account cap, all 10 platforms, webhooks, MCP, and BYO credentials. The getting started docs list social accounts, posts, scheduling, first comment scheduling, and media attachment as core features. The same docs show `GET /v1/social-accounts` for connected accounts and `POST /v1/posts` with `containers`, `socialAccountIds`, and optional `scheduledAt` for publishing or scheduling. Authentication docs say Outstand currently supports one authentication method, passes a Bearer credential in the `Authorization` header, returns an invalid or missing credential error when auth fails, and allows multiple named credentials per organization. The MCP docs say Outstand exposes 25 tools for posting, scheduling, analytics, media management, account management, and social network configuration. Outstand's MCP tool reference lists `create_post`, `list_posts`, `get_post`, `get_post_analytics`, `delete_post`, `create_reply`, and `get_replies` for post work. The MCP tool reference says `create_post` accepts `content`, `social_account_ids`, optional `scheduled_at` up to 30 days ahead, optional `media_ids`, optional `first_comment`, and optional `thread_content`. The media MCP workflow uses `upload_media`, HTTP PUT to the upload URL, `confirm_media_upload`, then `create_post` with the media ID. Confirmed media files are retained for 60 days. Post lifecycle docs track post-level `publishedAt` and `scheduledAt`, plus per-account `status`, `error`, `platformPostId`, and `publishedAt`. Immediate publishing starts within seconds, scheduled publishing starts at the scheduled time with a 30-second tolerance, and webhook delivery is sent within seconds after publishing completes. Webhook docs list `post.published` and `post.error` events plus an account re-authentication event. They send JSON payloads, can include `X-Outstand-Signature: sha256=` when signing is configured, and use HMAC-SHA256 over the raw request body. ## Comparison | Area | Outstand | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Teams that want social media management tools callable from AI agents. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Cross-platform publishing API with REST, webhooks, MCP, media, analytics, and BYO credentials. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | 10 supported publishing platforms, 25 MCP tools, posts, replies, media upload, account metrics, webhooks, and social network configuration. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Home page lists USD 5/month with 1,000 included posts, then USD 0.01 per post over 1,000, with all 10 platforms and connected accounts included. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for Bearer auth, connected accounts, `POST /v1/posts`, media upload/confirm, scheduled posts, per-account statuses, webhooks, and MCP tools. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Outstand fits cross-platform publishing, scheduling, media, analytics, and agent-managed social operations. | Xquik fits agents that need X search, extraction, account actions, monitors, webhooks, and exports. | | Primary use | Publish or schedule posts across 10 social platforms. | Tweet search results, follower exports, account actions & monitor events and action tools exposed through REST API, MCP, and the dashboard. | | Network strategy | Cross-platform publishing and account management. | Dedicated X task depth. | | Data movement | REST post objects, per-account publish state, analytics, media IDs, and webhook events. | CSV, JSON, XLSX, Markdown, webhook events, and API responses. | ## Operating model Compare Outstand and Xquik by output, cost, and handoff. Outstand fits cross-platform publishing, scheduling, media, analytics, and agent-managed social operations. Xquik fits agents that need X search, extraction, account actions, monitors, webhooks, and exports. Primary use: Publish or schedule posts across 10 social platforms. Xquik: Tweet search results, follower exports, account actions & monitor events and action tools exposed through REST API, MCP, and the dashboard. Network strategy: Cross-platform publishing and account management. Xquik: Dedicated X task depth. Data movement: REST post objects, per-account publish state, analytics, media IDs, and webhook events. Xquik: CSV, JSON, XLSX, Markdown, webhook events, and API responses. ## Current cost checkpoint Use Outstand units when the job is cross-platform social publishing. Use Xquik units when the job needs X search, follower exports, tweet replies, media upload, DMs, monitors, webhooks, SDKs, exports, or MCP from one X-focused product. | Task | Outstand unit to price | Xquik unit to price | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | Publish to multiple social platforms | Price the USD 5/month plan with 1,000 included posts and USD 0.01/post above that. | Use Xquik when the publishing job also needs X search, follower data, media upload, DMs, monitors, or exports. | | Schedule a post | `POST /v1/posts` accepts `scheduledAt`; MCP `create_post` accepts `scheduled_at` up to 30 days ahead. | Creating a tweet or reply costs 30 credits/call. Use REST, SDKs, MCP, or dashboard tools for X write handoffs. | | Attach media | Upload, PUT the file, confirm the upload, then attach the returned media ID to `create_post`; confirmed media files are retained for 60 days. | Use Xquik media upload when the same media must attach to X tweets or DMs from connected X accounts. | | Track publish state | Check per-account `status`, `error`, `platformPostId`, and `publishedAt`; use Outstand webhooks for `post.published`, `post.error`, and account re-authentication. | Use Xquik monitors and signed webhooks when the workflow depends on X account events, keyword events, stored events, or downstream exports. | | Search tweets or export followers | Outstand's public docs focus on publishing, scheduling, analytics, media, and account management. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | ## Xquik value to test For agent and automation comparisons, test the actual handoff: can it search tweets, fetch users, run extractions, post, monitor, and receive webhook events from one API key? Choose Xquik when one API key must handle search, user fetches, extractions, posts, monitors, and webhook events. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm agent access, API behavior, connected-account actions, export formats, webhook payloads, and the handoff to your production system. Then compare seats, hosting, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one repeated task. Validate that Xquik can run the data collection, account action, export, or webhook handoff before moving more agent or dashboard work. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Compare Outstand and Xquik by managed social channels, tool count, X actions, and API needs. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. # PhantomBuster Alternative for Follower Exports Source: https://docs.xquik.com/alternatives/phantombuster Compare PhantomBuster browser automations and follower exports with Xquik tweet search, replies, profiles, monitors, signed webhooks, REST APIs, SDKs, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether PhantomBuster or Xquik fits a specific X/Twitter workflow: collect profiles, send outreach, export followers, search tweets, monitor accounts, trigger webhooks, or hand data to an app or AI agent. This is a factual comparison and migration guide. Verify current PhantomBuster pricing, X/Twitter automations, API limits, and platform terms on the official PhantomBuster pages before buying. ## Quick answer You need no-code social automation across multiple networks, cloud automations, lead lists, scheduled Phantoms, and CRM-style outreach steps. You need focused X API tasks: tweet search, follower exports, direct messages, media uploads, 1-second monitors, signed webhooks, SDKs, MCP, and credit-priced API calls. ## Source-backed PhantomBuster scope PhantomBuster's official pricing page describes Trial, Start, Grow, and Scale plans with automation slots, monthly execution time, email credits, AI credits, URL finder credits, integrations, data extraction across 15+ platforms, 100+ automations and workflows, scheduled auto-refresh, and export limits. The page also states that PhantomBuster collects real-time data from platforms including LinkedIn, Twitter (X), and Instagram. PhantomBuster's official export help explains that each automation run produces result files. CSV combines results from all runs so far, JSON covers the most recent run, Free plan and Free Trial exports are limited to 10 rows, and paid plans unlock full CSV files, JSON files, and CSV URLs for Google Sheets or integrations. PhantomBuster's official API help describes launching an individual Phantom through `POST /agents/launch` with an Agent ID. It says the run uses the Phantom's saved configuration by default, optional launch arguments can override that setup, arrays are not supported, successful launches return status `200` with a Container ID, and Workflows cannot be launched through the API. PhantomBuster's official Twitter Auto Follow page describes cloud-based Twitter automation, Twitter Follower Collector, Twitter Following Collector, Twitter Hashtag Collector, Twitter Auto Unfollow, and Twitter Search Export. Its setup path includes connecting Twitter with the browser extension, providing Twitter profile URLs, choosing follow or unfollow behavior, setting profiles per launch, and receiving CSV or JSON output. ## Comparison | Area | PhantomBuster | Xquik | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Growth, sales, or operations teams need no-code social automation, prospecting flows, lead enrichment, and scheduled Phantoms. | Teams need X/Twitter records, account actions, monitor events, exports, signed webhooks, SDKs, and MCP from one X-focused platform. | | Product type | No-code cloud automation platform with Phantoms, Flows, execution time, and automation slots. | Tweet search results, follower exports, account actions, monitor events, signed webhooks, dashboard tools, REST APIs, SDKs & MCP. | | X/Twitter path | Pick a Twitter/X Phantom, connect the account or required session, run the Phantom, then export CSV/JSON result files or chain the result into another Phantom or integration. | Start with a dashboard tool, REST endpoint, SDK call, export, webhook subscription, or MCP tool for a defined X task. | | Returned data | CSV/JSON result files, Phantom run outputs, lead lists, and workflow artifacts to download or pass into another Phantom. | API responses, CSV/JSON/XLSX exports, monitor events, webhook payloads, action logs, and MCP responses. | | Cost model | Plans are based on automation slots and execution time. The public pricing page currently shows monthly and annual plan options. | Starter is USD 20/month with 140,000 included credits. Top-ups are USD 0.00015/credit, webhook management is free, and active monitors bill only while enabled. | | API fit | Official API docs cover launching Phantoms and managing containers, but state that Workflows cannot be launched through the API. | REST endpoints cover tweet search results, follower exports, account actions & monitor events, X write actions, monitors, webhooks, extractions, billing, accounts, support, and API keys. | | Summary | PhantomBuster is useful when the job is a multi-step no-code growth workflow across social and lead tools. | Xquik is useful when the job is specifically tweet search results, follower exports, account actions & monitor events, X account actions, monitoring, webhooks, exports, and agent handoff. | ## Workflow fit Compare PhantomBuster and Xquik by the actual job. PhantomBuster starts with a Phantom or Flow. Xquik starts with a concrete X task: search tweets, export followers, monitor an account, upload media, send a DM, deliver a webhook, or let an agent call Xquik through MCP. PhantomBuster: choose a Phantom or Flow and configure account/session inputs. Xquik: create an API key or use a dashboard tool for the X task. PhantomBuster: download or chain result files. Xquik: receive API responses, exports, monitor events, webhook payloads, or MCP tool output. PhantomBuster: chain Phantoms or export files. Xquik: call REST, poll events, receive signed webhooks, export files, use SDKs, or use MCP. ## Xquik workflows to test For an X/Twitter automation comparison, test the full path: setup, returned fields, pagination, export format, webhook delivery, retry behavior, and cost for the same workload. Export followers, following, verified followers, list members, community members, and related user lists. Use CSV, JSON, XLSX, or API responses. Search tweets by query, author filters, language, sort mode, date range, and cursor. Hand the records to a CRM, spreadsheet, warehouse, or agent. Track accounts or keywords with 1-second monitors, store events, poll by cursor, or deliver HMAC-signed webhook payloads to queues and CRMs. Post tweets, upload media, send DMs, like, retweet, follow, update profiles, or connect write actions to a product workflow. ## Trial checklist Use one job: follower export, tweet search, account monitor, DM send, media upload, or webhook delivery. In PhantomBuster, run the matching Phantom or Flow and inspect the result file. In Xquik, call the endpoint or dashboard tool and inspect the returned records. Confirm where the output goes next: CSV, JSON, XLSX, webhook, SDK call, MCP tool, CRM, spreadsheet, queue, or warehouse. Compare PhantomBuster execution time, automation slots, account/session work, Xquik credits, active monitor billing, and engineering time for retries and alerts. ## Migration path Do not migrate a whole automation stack first. Move one X/Twitter job with a clear owner and output contract. 1. Export the same sample from PhantomBuster and Xquik. 2. Compare IDs, usernames, timestamps, text fields, media links, pagination, errors, and retry states. 3. Replace a file export or manual download with a REST call, scheduled extraction, or signed webhook when Xquik already returns the fields your downstream system needs. 4. Keep PhantomBuster for broad no-code social automation that does not belong in an X-focused API. ## Official sources to verify Verify current plans, automation slots, execution time, and billing terms. Verify current API routes, Phantom launch behavior, containers, result files, and Workflow API limitations. Verify current CSV, JSON, CSV URL, Google Sheets, and result-chaining behavior. Verify current launch endpoint behavior, Agent IDs, Container IDs, and Workflow API limitations. Verify current Twitter/X daily limits and account-risk guidance before running automations. Verify current X/Twitter Phantoms, setup requirements, and supported workflow outputs. Verify included credits, top-ups, free operations, and active monitor billing. ## Xquik next steps Review authentication, endpoint groups, response conventions, pagination, and errors. Estimate costs, run jobs, paginate results, and export CSV, JSON, or XLSX files. Map tweet monitoring, signed webhooks, MCP agents, follower exports, and tweet composition to API calls. Deliver signed monitor events to queues, CRMs, Slack, databases, or internal services. # Pipedream Alternative for X API Workflows Guide Source: https://docs.xquik.com/alternatives/pipedream Compare Pipedream with Xquik for X/Twitter automation, tweet search, follower exports, webhooks, APIs, MCP, and workflow handoffs. Compare API coverage.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Pipedream, Xquik, or both fit a workflow that needs X/Twitter data, account actions, alerts, exports, webhooks, API calls, or custom component code. This is a factual comparison and migration guide. Verify current Pipedream pricing, Workflows credit rules, HTTP trigger behavior, component publishing, managed auth, and platform terms on official Pipedream pages before buying. ## Quick answer You need developer-friendly workflow automation with HTTP triggers, schedules, code steps, reusable actions, sources, and app handoffs. You need focused X API tasks: tweet search, follower exports, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, MCP, and credit-priced API calls. Pipedream should run the workflow while Xquik supplies tweet search results, follower exports, account actions & monitor events, write actions, monitor events, webhook payloads, exports, or API responses. ## Source-backed Pipedream scope Pipedream's official pricing docs say Workflows use a credit-based model for compute time during workflow execution, with one credit per 30 seconds at 256MB per workflow segment. The pricing docs also say Pipedream does not charge by number of steps, development and testing in the workflow builder are free, higher memory increases credit usage in 256MB intervals, and credits are charged when workflows execute. Pipedream's official workflow docs say every workflow begins with a trigger, HTTP triggers create a unique URL, each request executes the workflow, and code plus action steps run in order after the trigger. Pipedream's official trigger docs say HTTP triggers accept valid HTTP requests from anywhere, expose method, payload, headers, path, query, URL, and other request data in `steps.trigger.event`, support custom domains, and return `413 Payload Too Large` when the body exceeds the documented limit. Pipedream's official action quickstart says developers can publish private actions with `pd publish`, capture user input with props, use npm packages without a package file, export data from an action, and add app props for managed auth. ## Comparison | Area | Pipedream | Xquik | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Teams need workflow automation with HTTP triggers, schedules, code steps, reusable components, app actions, and event inspection. | Teams need X/Twitter records, account actions, monitor events, exports, signed webhooks, SDKs, and MCP from one X-focused platform. | | Product type | Developer workflow automation platform with triggers, actions, code steps, sources, components, managed auth, and CLI publishing. | Tweet search results, follower exports, account actions & monitor events, account actions, monitors, webhooks, exports, dashboard tools, REST API, SDKs & MCP. | | X/Twitter path | Build a Pipedream action or source that calls Xquik, then route the result through HTTP triggers, app actions, code, or schedules. | Start with a dashboard tool, REST endpoint, SDK call, export, webhook subscription, or MCP tool for a defined X task. | | Returned data | Step exports, `steps.trigger.event`, logs, action return values, source events, and downstream app payloads. | API responses, CSV/JSON/XLSX exports, monitor events, webhook payloads, action logs, and MCP responses. | | Cost model | Pipedream Workflows uses credits for compute time. Official docs currently describe one credit per 30 seconds at 256MB per workflow segment. | Starter is USD 20/month with 140,000 included credits. Top-ups are USD 0.00015/credit, webhook management is free, and active monitors bill only while enabled. | | API fit | Pipedream is the orchestration and component layer. It can host the Xquik action, receive webhook events, transform records, and call destination apps. | Xquik is the X API layer. It handles X-specific endpoints, pagination, exports, account actions, monitors, signed webhooks, SDKs, and MCP. | | Summary | Pipedream is useful when the main problem is running code-backed automation across apps and APIs. | Xquik is useful when the main problem is reliable tweet search results, follower exports, account actions & monitor events, X account actions, monitoring, webhooks, exports, and agent handoff. | ## Best combined workflow For X/Twitter automation, Pipedream and Xquik fit together when the workflow needs both custom code and a focused X API contract. Let Pipedream own triggers, event inspection, code steps, app actions, component packaging, and destination handoffs. Let Xquik own tweet search, user lookups, follower exports, media uploads, DMs, monitor events, signed webhooks, API response contracts, and MCP. Pipedream: create the workflow, trigger, custom action, source, and destination steps. Xquik: create one API key for the X task. Pipedream: expose step exports and event objects. Xquik: return tweet records, user records, exports, monitor events, webhook payloads, or API responses. Pipedream: send results to apps, queues, databases, Slack, or custom code. Xquik: supply REST, signed webhooks, SDKs, exports, and MCP. ## Xquik workflows to run from Pipedream Use Xquik inside Pipedream when the workflow needs a concrete X-specific API step before routing data to other apps. Call `GET /x/tweets/search`, filter records in a code step, then post matches to Slack, a queue, or a database. Create an extraction job, poll until `completed`, then fetch rows or export CSV, XLSX, or JSON for a warehouse step. Register a Pipedream HTTP trigger or source URL in Xquik, then emit signed account or keyword monitor payloads with stable IDs. Publish a private `xquik-search-tweets`, `xquik-create-monitor`, or `xquik-create-extraction` action with the Pipedream CLI. ## Component path Start with the smallest reusable component package. Add endpoints only after workflows repeatedly need them. | Need | Pipedream path | Xquik detail | | ---------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------- | | One workflow call | Code step or HTTP action | Call a Xquik REST endpoint and export the parsed response. | | Reusable operation | Action component | Define props, call Xquik, publish with `pd publish`, and return records for downstream steps. | | Monitor event webhooks | HTTP trigger or source | Register the Pipedream endpoint in Xquik and read event fields from `steps.trigger.event`. | | Batch export | Scheduled workflow or polling source | Create extraction jobs, poll status, then fetch details or export files. | ## Monitor webhook receiver handoff When a Pipedream HTTP trigger or source receives Xquik monitor events, verify `X-Xquik-Signature` before exporting data to later steps. Store `deliveryId` and `streamEventId` as separate workflow keys: use `deliveryId` for endpoint retry de-dupe and `streamEventId` when one monitor event should process once across source or workflow changes. Return `2xx` after accepting a duplicate `deliveryId` or `streamEventId`; later code or action steps can skip the already-processed event. Keep shared step exports to `deliveryId`, `streamEventId`, `eventType`, `occurredAt`, `username` or `query`, and mapped tweet fields. Do not store endpoint signing values, raw request body, raw signature, or full headers in logs, data stores, Slack messages, CRM rows, or retry queues. ## Trial checklist Use one task: tweet search, follower export, monitor alert, media upload, DM send, or account action. Add the trigger, Xquik action or code step, field mapping, destination app, and failure path. Confirm returned fields, pagination, `Retry-After` handling, webhook signature verification, export format, and downstream mapping. Compare Pipedream compute credits, memory, workflow segments, Xquik credits, active monitor billing, and engineering time for retries and alerts. ## Migration path Do not replace a working Pipedream workflow first. Replace only the brittle X/Twitter step. 1. Keep the trigger, destination app steps, code transforms, and field mappings. 2. Replace a manual export, unofficial scraper, or custom X request with a Xquik REST call, extraction, webhook, or reusable action component. 3. Map Xquik fields into the existing step export shape. 4. Add explicit paths for `401`, `402`, `429`, and `5xx` responses. 5. For monitor events, verify signatures before emitting downstream events. ## Official sources to verify Verify Workflows credit rules, workflow segments, compute time, memory, and testing/development billing. Verify triggers, steps, code actions, step exports, and workflow execution model. Verify HTTP endpoint behavior, `steps.trigger.event`, auth options, response behavior, and payload limits. Verify action components, props, managed auth, Pipedream CLI setup, and `pd publish`. ## Xquik next steps Build Xquik Pipedream actions, monitor-event sources, extraction polling, and error handling. Export followers, map CRM fields, and hand off CSV, XLSX, or JSON. Deliver signed monitor events to Pipedream, queues, CRMs, Slack, databases, or backend services. Check included credits, top-ups, free operations, and active monitor billing. # Post Bridge Alternative for X Cross-Posting APIs Source: https://docs.xquik.com/alternatives/post-bridge Compare Post Bridge cross-posting, media scheduling, and paid API access with Xquik tweet search, follower exports, monitors, webhooks, SDKs, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Post Bridge or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Post Bridge pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team needs cross-posting through a social scheduler, a paid API add-on, direct uploads, and platform-specific media handling. You need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, or MCP from one product. ## Source-backed Post Bridge scope Post Bridge's official API overview says the API is live and enables automation of social media posting and management tasks through applications and workflows. The API overview says API access requires an active Post Bridge subscription and the API add-on. It lists the add-on at USD 5/month in addition to the plan subscription. The same page points users to the billing page to enable the API add-on, a dashboard page for API credentials, API documentation, and Discord support through a dedicated API channel. Post Bridge's cross-posting guide says users upload video content once, connect each social media account, and Post Bridge distributes the content to connected platforms. The cross-posting guide says content must be uploaded directly through Post Bridge. It does not support reposting Instagram collaborative reels, already-live social posts, or videos from other platforms or channels. The thread scheduling guide says Post Bridge does not currently support Twitter/X threads, Instagram Threads threaded posts, or split tweets. It says users can still schedule individual posts to X and Instagram Threads. The platform limits page lists 100 scheduled posts/hour per user, MP4 or MOV video uploads, 9:16, 16:9, 1:1, and 4:3 video ratios, 3-second minimum and 300-second maximum videos, 500MB total upload size on Pro, JPG or PNG images, 8MB maximum per image, and 35 total images. ## Comparison | Area | Post Bridge | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Creators and teams that want cross-posting from one social media scheduler. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Social scheduler with a paid API add-on for posting and management automation. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Official support docs cover API add-on access, direct-upload cross-posting, individual X posts, media limits, scheduled-post limits, and platform restrictions. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Official API docs list the API add-on at USD 5/month in addition to an active plan. Value depends on the plan, direct-upload workflow, media limits, and whether thread scheduling matters. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for direct upload, connected social accounts, API add-on access, media constraints, and no current X thread scheduling. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Post Bridge helps teams schedule and cross-post directly uploaded social content across connected networks. | Xquik fits work that needs X search, extraction, follower exports, monitors, webhooks, SDKs, and MCP tools. | | Primary use | Upload once, schedule, and cross-post across connected social platforms. | Run tweet search, follower exports, media posts & account monitors, publishing, monitoring, and exports. | | API use case | Social post publishing and management automation through a paid add-on. | Operational X API calls for reads, writes, monitors, events, and draws. | | Automation channel | API and scheduler distribution. | MCP, SDKs, webhooks, REST API, and dashboard tools. | ## Operating model Compare Post Bridge and Xquik by output, cost, and handoff. Post Bridge helps teams schedule and cross-post directly uploaded social content across connected networks. Xquik fits work that needs X search, extraction, follower exports, monitors, webhooks, SDKs, and MCP tools. Primary use: Upload once, schedule, and cross-post across connected social platforms. Xquik: Run tweet search, follower exports, media posts & account monitors, publishing, monitoring, and exports. API use case: Social post publishing and management automation through a paid add-on. Xquik: Operational X API calls for reads, writes, monitors, events, and draws. Automation channel: API and scheduler distribution. Xquik: MCP, SDKs, webhooks, REST API, and dashboard tools. ## Current cost checkpoint Use current Post Bridge limits when the job is direct-upload social scheduling or cross-posting. Use Xquik units when the job needs X search results, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, exports, or MCP. | Task | Post Bridge unit to price | Xquik unit to price | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Add API automation to a scheduler | Official API docs list a USD 5/month API add-on in addition to an active Post Bridge plan. | Xquik includes REST API access with plans. Use API calls for X reads, writes, monitors, events, webhooks, SDKs, exports, and MCP. | | Cross-post uploaded videos | Upload video content directly to Post Bridge, connect each social account, and distribute to connected platforms. Already-live posts and external videos are not supported for automatic reposting. | Use Xquik when the same workflow needs X media upload, tweet creation, DMs, search results, follower data, monitors, or webhook events. | | Schedule X or Threads content | Individual X and Instagram Threads posts can be scheduled, but official docs say X threads, Instagram Threads threaded posts, and split tweets are not currently supported. | Creating a tweet or reply costs 30 credits/call. Use Xquik for tweet replies, media uploads, DMs, search, follower exports, monitors, webhooks, SDKs, and MCP. | | Process media | Official limits list 100 scheduled posts/hour per user, MP4 or MOV video, 3 to 300 second videos, 500MB total upload size on Pro, JPG or PNG images, 8MB/image, and 35 total images. | Use the media upload API for X media, then attach media to tweet or DM write actions from a connected X account. | | Search tweets or export followers | Not a Post Bridge core public API unit; validate source-data and export needs before using it as the automation layer. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | ## Xquik value to test For publishing comparisons, test what happens before and after the post: find source tweets, upload media, send DMs, export replies, monitor keywords, and notify downstream tools. Choose Xquik when publishing must include that data loop. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm draft flow, media handling, scheduling needs, export formats, API access, and whether data or monitoring matters after publishing. Then compare seats, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one publishing or reporting task. Keep the content calendar stable while you validate exports, API access, monitoring, and alerts in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Compare Post Bridge and Xquik by publishing channels, API add-ons, tweet, profile, follower, reply & timeline depth, and monitoring needs. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. # Postproxy Alternative for Tweet Publishing Source: https://docs.xquik.com/alternatives/postproxy Compare Postproxy with Xquik for tweet publishing, tweet search, follower exports, replies, monitors, signed webhooks, REST APIs, SDKs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Postproxy or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Postproxy pricing, post limits, supported platforms, and product terms on the [official pricing page](https://postproxy.dev/pricing/) before buying. ## Quick answer Your team mainly needs one publishing API for multi-platform posts, queues, analytics, publish logs, webhooks, MCP, or workflow tools. You need publishing plus tweet search, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, and MCP in the same account. ## Source-backed Postproxy scope Postproxy's official homepage describes one API for creating social posts across Facebook, Instagram, TikTok, LinkedIn, YouTube, X, Threads, and Pinterest. It also positions MCP, skills, n8n, Zapier, Make, Needle, and other workflow tools as publishing integrations. The official API overview says Postproxy can create and manage social media posts across Facebook, Instagram, TikTok, LinkedIn, YouTube, X, and Threads. The getting-started guide describes built-in scheduling, error handling, retry management, authentication, and a connected social account requirement. The official pricing page lists Free, Build, Scale, and Enterprise plans. Free includes 2 profile groups and 10 posts/month. Build lists 10 profile groups, 120 posts/month, comments, analytics, 30-day publish logs, and webhooks at USD 17/month. Scale lists 50 profile groups, no listed monthly post-count cap, 180-day publish logs, webhooks, and priority support at USD 99/month. Enterprise starts at USD 699/month. The pricing page says one published post counts as one post even when cross-posted to multiple platforms, and rate limit quotas from Postproxy and social platforms still apply. Postproxy webhooks cover `post.processed`, `platform_post.published`, `platform_post.failed`, `platform_post.failed_waiting_for_retry`, `platform_post.insights`, `profile.disconnected`, `profile.connected`, and `media.failed`. Postproxy's X guide says X has strict rate limits, separate media upload limits, and shared-app publishing limits. The BYO developer credentials guide says connecting X profiles with your own X developer credentials exempts those profiles from the shared 24-hour posting quota, subject to X credit balance and platform per-app limits. ## Comparison | Area | Postproxy | Xquik | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Teams that want one API for publishing posts across Facebook, Instagram, TikTok, LinkedIn, YouTube, X, Threads, and Pinterest. | Teams that need X tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Social publishing API. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Create and manage social posts, profile groups, queues, comments, analytics, publish logs, webhooks, media URLs, MCP, skills, workflow tools, and publishing retries across supported platforms. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Official pricing lists Free at USD 0 for 10 posts/month, Build at USD 17/month for 120 posts/month, Scale at USD 99/month with no listed monthly post-count cap, and Enterprise from USD 699/month. Twitter/X shared publishing can be quota-bound unless profiles use their own X developer credentials. | Xquik starts at USD 20/month with 140,000 included credits. Tweet search and follower exports cost 1 credit/result. Common X write calls cost 10 credits/call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Use Bearer auth, profile groups, post queues, media URLs, per-platform parameters, webhooks, and publish status handling. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Postproxy provides a unified social media publishing API for posting to X, Instagram, LinkedIn, TikTok, Threads, YouTube, Facebook, and Pinterest. | Xquik fits teams that need X search, extractions, monitors, signed webhooks, SDKs, MCP, dashboard tools, and X write actions. | | Channel scope | Multi-platform publishing through one social API. | Tweet search, follower exports, media posts & account monitors, publishing, monitors, and exports. | | Data needs | Publishing status, retries, and per-platform outcomes. | Search, follower exports, extractions, monitors, webhooks, and event logs. | | Agent support | Publishing integrations for MCP, n8n, Zapier, and skills. | MCP, REST API, SDKs, webhooks, and dashboard tools for X tasks. | ## Operating model Compare Postproxy and Xquik by output, cost, and handoff. Postproxy provides a unified social media publishing API for posting to X, Instagram, LinkedIn, TikTok, Threads, YouTube, Facebook, and Pinterest. Xquik fits teams that need X search, extractions, monitors, signed webhooks, SDKs, MCP, dashboard tools, and X write actions. Channel scope: Multi-platform publishing through one social API. Xquik: Tweet search, follower exports, media posts & account monitors, publishing, monitors, and exports. Data needs: Publishing status, retries, and per-platform outcomes. Xquik: Search, follower exports, extractions, monitors, webhooks, and event logs. Agent support: Publishing integrations for MCP, n8n, Zapier, and skills. Xquik: MCP, REST API, SDKs, webhooks, and dashboard tools for X tasks. ## Current cost checkpoint Use the current Postproxy plan limits when the job is social publishing. Use Xquik units when the job needs tweet search, follower exports, media posts & account monitors, exports, monitors, or account actions beyond publishing. | Task | Postproxy unit to price | Xquik unit to price | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Publish 100 posts across social channels | Build includes 120 posts/month at USD 17/month. Cross-posting the same content to multiple platforms counts as 1 post in Postproxy. | Create tweet or reply costs 30 credits/call. Upload media separately when needed, also 10 credits/call. Xquik publishes to connected X accounts. | | Publish more than 120 posts/month | Scale is listed at USD 99/month with no listed monthly post-count cap, subject to Postproxy and platform quotas. Postproxy says X shared publishing includes 20 posts per 24 hours, while BYO developer credentials use the account's X credits and app limits. | Price each X write call. Top-up credits cost USD 0.00015/credit, so a 30-credit create-tweet call costs USD 0.0045 before included monthly credits. | | Search tweets or export followers | Not a Postproxy core task; it is a publishing API. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | | Monitor accounts and deliver events | Use Postproxy webhooks for post events. | Active monitors check every 1 second and cost 21 credits per monitor-hour. Stored events and signed webhook delivery management are included. | Choose Postproxy when the main task is multi-platform publishing. Choose Xquik when X is the main channel and the workflow needs reads, exports, DMs, media uploads, 1-second monitors, signed webhooks, SDKs, or MCP. ## Xquik value to test For API comparisons, price the whole task: endpoint access, pagination, retries, storage, exports, alerts, webhooks, SDKs, and maintenance. Choose Xquik when those pieces must ship together. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm returned data, pagination, retry behavior, webhook payloads, export formats, and API ergonomics. Then compare total cost for the same task: access, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Postproxy Webhook & Quota Trial Test one profile group before migrating a publishing queue. Connect only the social profiles required for the trial. Keep the Postproxy profile-group ID beside each scheduled post. Publish one text post and one media post. Cross-post each item only where the campaign requires it. Record the Postproxy post ID and every platform-specific result. Subscribe a test receiver to Postproxy post events. Capture these documented states when they occur: * `post.processed` for the completed parent post job. * `platform_post.published` for a successful platform result. * `platform_post.failed` for a final platform failure. * `platform_post.failed_waiting_for_retry` during retry handling. * `platform_post.insights` when refreshed engagement becomes available. * `profile.disconnected` when a connected profile needs attention. * `media.failed` when an attached media item cannot publish. Store the event name, Postproxy post ID, platform, delivery time, and receiver result. This separates a queue failure from an X-only publishing failure. Run the X portion once with shared publishing limits. Run it again with your own X developer credentials when the trial permits. Compare accepted posts, retries, X credit use, and platform errors. Do not infer quota behavior from monthly Postproxy post limits alone. Use Xquik for the surrounding X workflow. Search source tweets before writing. Retrieve the published Tweet ID afterward. Then export replies, quotes, reposts, and likes through their specific routes. Use an account monitor when new post alerts must continue after the publishing job completes. ## Migration path Start with one API-backed task. Keep the current integration running while you compare output shape, latency, pagination, retries, exports, and alert delivery in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Confirm Xquik returns every required tweet, reply, follower, profile, and media field. Switch only when the handoff needs less code or costs less. Price the real workload. On Postproxy, price monthly post volume, profile groups, publish logs, comments, analytics, webhooks, and platform quota needs. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current plan limits, post caps, and platform terms before making a final decision. # Postwise Alternative for Tweet Scheduling APIs Source: https://docs.xquik.com/alternatives/postwise Compare Postwise with Xquik for tweet scheduling, tweet search, follower exports, replies, monitors, signed webhooks, REST APIs, SDKs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Postwise or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Postwise pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team mainly needs AI post generation, a scheduled creator queue, custom AI voices, analytics, or posts across Twitter/X, LinkedIn, and Threads. You need publishing plus tweet search, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, and MCP in the same account. ## Source-backed Postwise scope Postwise's official pricing page lists integrations for Twitter, LinkedIn, and Meta Threads. It describes Postwise pricing as simple, transparent pricing plans. Basic is listed at USD 37/month. It includes 3 social accounts, 500 AI-generated posts/month, 3 months scheduling, 3 custom AI voices, and the GhostWriter AI Assistant. Boss is listed at USD 59/month. It includes 5 social accounts, 1,000 AI-generated posts/month, 12 months scheduling, and an Advanced Analytics Dashboard. The top visible plan is listed at USD 97/month. It lists no social-account cap, no AI-generated-post cap, no scheduling cap, and Custom AI Training. The pricing comparison lists GhostWriter AI on all plans, Basic, Advanced, and Enterprise-grade analytics tiers, Custom AI Training on the top plan, and email, chat, or priority support by plan. Postwise's pricing FAQ says users can upgrade or downgrade plans, annual subscriptions receive a 20% discount, monthly subscriptions have no long-term commitment, the 7-day trial converts to the selected plan, and the subscription can be canceled before the trial ends. Postwise's home page positions the product for Twitter/X, LinkedIn, and Threads. It describes AI content creation, scheduling across platforms, viral post repurposing, engagement tracking, and a multi-platform dashboard. Postwise's Twitter scheduler page lists smart scheduling, analytics and insights, team collaboration, multi-account management, tweet threading, and retweet scheduling. Its page-specific pricing block lists Pro at USD 37/month with smart scheduling for 3 Twitter accounts, support for X, Threads, and LinkedIn, and LinkedIn Company Pages; it lists Enterprise at USD 97/month with advanced Twitter analytics and reporting plus priority support. Confirm current limits at checkout because official pages expose more than one plan grid. ## Comparison | Area | Postwise | Xquik | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Creators who want a writing system and scheduled queue. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | AI writing, social scheduling, analytics, and creator-growth tool for Twitter/X, LinkedIn, and Threads. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Official pages list AI-generated posts, scheduling windows, social-account limits, custom AI voices, GhostWriter AI, analytics tiers, Custom AI Training, support tiers, viral post repurposing, engagement tracking, tweet threading, and retweet scheduling. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Official pricing lists Basic at USD 37/month, Boss at USD 59/month, and a top plan at USD 97/month, plus a 20% annual discount. Value depends on AI-generated post volume, social-account count, scheduling horizon, analytics, custom AI voices, and training needs. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for handoffs when content needs data, exports, monitoring, alerts, or automation beyond the calendar. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Postwise focuses on AI post generation, scheduling, analytics, and creator growth. | Xquik adds tweet search, follower exports, write actions, 1-second monitors, API authentication, and signed webhooks. | | Primary use | Write and schedule creator posts. | Run tweet search, follower exports, media posts & account monitors, write, and monitor tasks and monitor accounts. | | Output | AI-generated posts, scheduled content, analytics, and creator workflow output. | Published actions, extracted datasets, and event streams. | | Integration points | Creator dashboard, platform schedulers, and page-specific growth tools. | REST API, webhooks, MCP, and dashboard tools. | ## Operating model Compare Postwise and Xquik by output, cost, and handoff. Postwise focuses on writing assistance, scheduling, and creator content work. Xquik adds tweet search, follower exports, write actions, 1-second monitors, API authentication, and signed webhooks. Primary use: Write and schedule creator posts. Xquik: Run tweet search, follower exports, media posts & account monitors, write, and monitor tasks and monitor accounts. Output: Scheduled content and writing output. Xquik: Published actions, extracted datasets, and event streams. Integration points: Creator dashboard. Xquik: REST API, webhooks, MCP, and dashboard tools. ## Current cost checkpoint Use current Postwise plan limits when the job is creator content generation or a scheduled social queue. Use Xquik units when the job needs X search results, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, exports, or MCP. | Task | Postwise unit to price | Xquik unit to price | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Generate creator posts | Basic is USD 37/month with 500 AI-generated posts/month, 3 social accounts, 3 months scheduling, 3 custom AI voices, and GhostWriter AI. | Compose, refine, and score are free. Use Xquik when the generated draft needs tweet search, follower exports, media posts & account monitors, a connected-account post, or an API handoff. | | Schedule higher-volume content | Boss is USD 59/month with 1,000 AI-generated posts/month, 5 social accounts, 12 months scheduling, and Advanced Analytics Dashboard. | Creating a tweet or reply costs 30 credits/call from a connected X account. Media upload, DMs, and other write actions have their own documented costs. | | Use top-plan AI and account limits | The top visible plan is USD 97/month and lists no social-account, AI-generated-post, or scheduling cap, plus Custom AI Training. Confirm checkout because official pages expose more than one plan grid. | Use Xquik when the workflow needs tweet search, follower exports, monitors, signed webhooks, SDK calls, MCP, or stored event records. | | Search tweets or export followers | Not a Postwise core public pricing unit; validate source-data and export needs during trial. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | | Monitor X accounts or keywords | Postwise pages focus on scheduling, analytics, and creator growth tools. Validate monitoring needs before buying. | Active monitors check every 1 second and cost 21 credits per monitor-hour. Stored events and signed webhook delivery management are included. | | Connect backend jobs or agents | Validate API, webhook, export, SDK, and MCP needs before buying. | REST API, signed webhooks, 10 SDKs, MCP, and CSV/JSON/XLSX/Markdown exports. | ## Postwise Creator Calendar Trial Test Postwise with a real 14-day creator calendar. Include standalone posts, a thread, one repost, and one scheduled media post. Use the same connected X profile throughout the trial. Create 2 custom AI voices with intentionally different tones. Draft the same topic with both voices. Record which draft needs fewer factual, structural, and tone edits before approval. Schedule posts across morning and evening windows. Move one post after review. Cancel another before publication. Confirm how each change appears inside the calendar and analytics dashboard. Keep this evidence for every trial post: | Evidence | What to record | | ----------- | ------------------------------------------------------------------ | | Draft | Prompt, selected AI voice, generated copy, and final approved copy | | Schedule | Connected profile, planned time, timezone, and changed time | | Format | Standalone post, thread, repost, link, or media post | | Publication | Published post URL, X Tweet ID, and observed publication time | | Review | Likes, reposts, replies, quotes, and the measurement timestamp | Postwise fits when writers own those calendar decisions. Xquik fits when an application must search source tweets or retrieve profiles first. It also fits when the application must export replies or followers after publication. Use Xquik monitors when a published campaign needs new-tweet alerts. Use signed webhooks when another service must receive each stored event. Keep those jobs separate from Postwise voice generation and calendar review. ## Test the Postwise-to-Xquik Handoff Generate the post in Postwise. Record its voice, format, target profile, scheduled time, and final approved text. Search tweets with Xquik when the draft cites a live conversation. Save the Tweet IDs, authors, URLs, media, and retrieval time. Store the published Tweet ID. Retrieve replies, quotes, reposts, and likes through their specific Xquik routes. Create an account or keyword monitor. Send matching events through a signed webhook to the campaign owner. ## Migration path Start with one publishing or reporting task. Keep the content calendar stable while you validate exports, API access, monitoring, and alerts in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Confirm Xquik returns every required tweet, reply, follower, profile, and media field. Switch only when the handoff needs less code or costs less. Compare live Postwise plans with Xquik subscription and usage options. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current plan limits, AI-generated post volume, social accounts, scheduling windows, analytics, and trial behavior. # SocialCrawl Alternative for Profile & Tweet APIs Source: https://docs.xquik.com/alternatives/socialcrawl Compare SocialCrawl profile, tweet, and community APIs across networks with Xquik follower exports, X write actions, monitors, signed webhooks, SDKs, and MCP.
For the complete documentation index, see llms.txt.
## Distinct SocialCrawl Fit SocialCrawl centers on normalized social data across many networks. Its envelope, computed fields, credit tiers, OpenAPI, and agent packages matter most. Use this page when multi-platform collection outranks X-specific depth. Use this guide to decide whether SocialCrawl or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current SocialCrawl pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team needs a cross-network social data API, normalized response envelopes, computed fields, credit tiers, and agent packages. You want collection, exports, monitoring, account operations, API access, and webhook delivery in one maintained API and dashboard. ## Source-backed SocialCrawl scope SocialCrawl's official docs describe it as a unified social media data API for developers and AI agents. Current public SocialCrawl surfaces use different coverage counts: the docs introduction says 21 platforms and 108 endpoints, while the pricing page says 27 platforms and 133 APIs. The docs introduction says responses use a consistent format across platforms and include computed fields such as `engagement_rate`, `language`, `content_category`, and `estimated_reach`. Authentication docs say requests use the `x-api-key` header, missing or malformed credentials return `401 MISSING_API_KEY` or `401 INVALID_API_KEY`, and accounts can keep up to 5 active keys. The credits page describes credit-based pay-as-you-go billing with a 50 concurrent request ceiling per credential. Standard requests cost 1 credit across 84 endpoints, Advanced requests cost 5 credits across 18 endpoints, and Premium requests cost 10 credits across 6 endpoints. The response schema includes `success`, `platform`, `endpoint`, `data`, `credits_used`, `credits_remaining`, `request_id`, and `cached`. List endpoints return `items`, optional `next_cursor`, and optional `total`. SocialCrawl docs say cache hits cost 0 credits, every response includes `credits_remaining`, `GET /v1/credits/balance` costs 0 credits, and idempotent replays deduct 0 new credits within a 24-hour TTL. The pricing page lists Free with 400 one-time credits, Starter with 2,500 credits for GBP 15, Growth with 20,000 credits for GBP 49, Pro with 150,000 credits for GBP 299, and Enterprise with custom credits. The AI agent docs publish `/llms.txt`, `/llms-full.txt`, OpenAPI JSON and YAML, plus per-platform llms files. The Twitter/X llms file lists profile, user tweets, tweet detail, community detail, community tweets, video transcript, and AI-powered X search endpoints. The Skills & MCP docs list a `socialcrawl-mcp` package on npm and MCP Registry with 4 tools: `socialcrawl_list_platforms`, `socialcrawl_list_endpoints`, `socialcrawl_request`, and `socialcrawl_get_docs`. ## Comparison | Area | SocialCrawl | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Builders who need broad social data access across many networks. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Cross-network social data API with Skills and MCP packages. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Official surfaces currently list either 21 platforms with 108 endpoints or 27 platforms with 133 APIs; validate the platform list for the workload. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Credit packs range from Free 400 credits to Pro 150,000 credits for GBP 299, with request tiers at 1, 5, and 10 credits. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for `x-api-key` auth, normalized envelopes, credit headers, idempotent retries, optional cursors, agent packages, and X/Twitter endpoint coverage. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | SocialCrawl fits cross-network social data collection when a unified envelope, computed fields, OpenAPI, llms files, Skills, and MCP matter. | Xquik is narrower but deeper for X tasks: account actions, follower exports, monitors, webhooks, SDKs, MCP, and dashboard tools. | | Platform coverage | Multi-platform social data with current public count drift between docs and pricing pages. | X reads, write actions, extractions, and monitors. | | Agent fit | Skills and MCP packages for broad social collection. | MCP and REST tools for tweet search, profile lookup, follower or reply exports, account actions, and monitors. | | Scope | Cross-network profiles, posts, comments, search, transcripts, ad libraries, and computed fields. | X search, account monitors, giveaway draws, exports, and signed webhooks. | ## Operating model Compare SocialCrawl and Xquik by output, cost, and handoff. SocialCrawl fits cross-network social data collection when a unified envelope, computed fields, OpenAPI, llms files, Skills, and MCP matter. Xquik is narrower but deeper for X tasks: account actions, follower exports, monitors, webhooks, SDKs, MCP, and dashboard tools. Platform coverage: Multi-platform social data with current public count drift between docs and pricing pages. Xquik: X reads, write actions, extractions, and monitors. Agent fit: Skills and MCP packages for broad social collection. Xquik: MCP and REST tools for tweet search, profile lookup, follower or reply exports, account actions, and monitors. Scope: Cross-network profiles, posts, comments, search, transcripts, ad libraries, and computed fields. Xquik: X search, account monitors, giveaway draws, exports, and signed webhooks. ## Current cost checkpoint Use SocialCrawl units when the job needs cross-network social data with normalized responses. Use Xquik units when the job is X-specific and needs search, follower exports, media upload, DMs, write actions, monitors, webhook events, SDKs, exports, or MCP. | Task | SocialCrawl unit to price | Xquik unit to price | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Run broad social data collection | Confirm the needed platform on current docs because public counts differ between docs and pricing pages. | Use Xquik when the job is specifically X search, users, followers, media, DMs, posts, monitors, events, or exports. | | Parse returned data | Responses use a consistent envelope with `data`, `credits_used`, `credits_remaining`, `request_id`, `cached`, and optional list cursors. | Xquik returns task-specific X API responses, extraction rows, export files, monitor events, and webhook payloads. | | Price request volume | Standard, Advanced, and Premium requests cost 1, 5, and 10 credits, with 400 Free credits and paid packs from GBP 15 to GBP 299. | Many Xquik read calls cost 1 credit/result or call. Creating a tweet or reply costs 30 credits/call, and active monitors cost 21 credits/hour while enabled. | | Connect agents | SocialCrawl publishes OpenAPI, llms files, Skills, and a 4-tool MCP server for its social data API. | Xquik publishes OpenAPI, SDKs, MCP, workflow docs, signed webhooks, and exports for X-specific operations. | | Search X or export followers | SocialCrawl's Twitter/X llms file lists profile, user tweets, tweet details, communities, video transcripts, and AI-powered X search. | Xquik adds follower exports, tweet search exports, media upload, DMs, write actions, monitors, webhooks, SDKs, and MCP handoffs. | ## Xquik value to test For API comparisons, price the whole task: endpoint access, pagination, retries, storage, exports, alerts, webhooks, SDKs, and maintenance. Choose Xquik when those pieces must ship together. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm returned data, pagination, retry behavior, webhook payloads, export formats, and API ergonomics. Then compare total cost for the same task: access, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Run the same query in both products, then compare record completeness, deduplication, error states, and export shape. Move the task only after downstream consumers accept the Xquik output. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Compare SocialCrawl and Xquik by platform coverage, X endpoint depth, export formats, and agent tools. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. # Sprinklr Alternative for X Monitoring | Monitor API Source: https://docs.xquik.com/alternatives/sprinklr Compare Sprinklr with Xquik for social monitoring, tweet search, follower exports, post tweets and replies, signed webhooks, REST APIs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Sprinklr or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Sprinklr pricing, access rules, and product terms on the official site before buying. ## Quick answer You need cross-channel planning, approvals, reporting, listening, or social care operations that span more than X. You need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, or MCP without buying a broad social suite. ## Source-backed Sprinklr scope Sprinklr's official Social page describes publishing, engagement, listening, paid media, compliance, and analytics across 30+ channels, plus integrations with CRM, digital asset management, business intelligence, and content management tools. Sprinklr's official Social Publishing and Engagement page describes content planning, a digital asset manager, an editorial calendar, an omnichannel publisher, an engagement dashboard, reporting and analytics, UGC management, granular access controls, custom approval workflows, AI-powered compliance, multi-level approvals, crisis workflows, rules, and automations. Sprinklr's official Social Listening page describes real-time listening across 30+ social and digital channels, visual brand mentions, sentiment analysis, emotion analysis, entity identification, text classification, competitor benchmarking, crisis alerts, report scheduling, and report exports. Sprinklr's API help describes REST JSON APIs, OAuth 2.0, a developer portal, Enterprise license requirements, pre-approved use cases, and extra enablement for Twitter Syndication, Case Compliance API, or Listening API when those data sets are required. Sprinklr's official Social and Publishing pages point buyers to a request-demo motion for enterprise social management. Its API help says Sprinklr customers need an Enterprise license, pre-approved use cases in the License Order Form, a developer who can work with REST JSON APIs, developer portal setup, and OAuth authorization through the Sprinklr UI. Twitter Syndication, Case Compliance API, and Listening API may require extra enablement when those data sets are part of the use case. ## Current access checkpoint Use this checkpoint before comparing Sprinklr with Xquik: | Job | Sprinklr access signal | Xquik access signal | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Buy enterprise social management | Request a demo and plan for enterprise procurement, governance setup, and rollout ownership. | Starter is USD 20/month with 140,000 credits for X tasks and API access. | | Connect social data to internal systems | Confirm Enterprise license scope, LOF use cases, developer portal setup, OAuth authorization, and approved data sets. | Xquik includes REST authentication, endpoints, SDKs, MCP, exports, and signed webhooks. | | Run X/Twitter data workflows | Verify whether Twitter Syndication, Case Compliance API, or Listening API enablement is required. | Tweet search, follower exports, monitor events, CSV/JSON/XLSX files, and webhook delivery use the Xquik credit model. | | Separate broad suite work from X jobs | Keep Sprinklr for cross-channel governance, approvals, reporting, listening, care, and compliance. | Use Xquik when the job is tweet and profile records, follower exports, monitor events & webhooks, account actions, monitors, webhooks, SDKs, MCP, or export handoffs. | ## Comparison | Area | Sprinklr | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Large enterprises with complex customer-care and governance requirements. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Enterprise social media management, listening, publishing, engagement, analytics, advertising, care, and compliance suite. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Sprinklr official pages describe publishing, engagement, listening, paid media, compliance, analytics, integrations, approval workflows, crisis workflows, reports, exports, and Enterprise API access. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Check seat count, contract minimums, add-ons, onboarding fees, and implementation time. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for procurement, admin setup, approval flows, reporting needs, and developer handoffs. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Sprinklr is an enterprise platform for customer experience, care, listening, and social operations. | Xquik is focused on tweet and profile records, follower exports, monitor events & webhooks, writes, monitors, and webhooks instead of a full CX suite. | | Scope | Broad enterprise customer experience platform. | Tweet and profile records, follower exports, monitor events & webhooks, extractions, monitors, and webhooks. | | Implementation | Enterprise rollout and governance model. | Account setup, dashboard tools, and API keys. | | Organization fit | Global social and care organizations. | Teams that need a practical X API, monitor, and export setup. | ## Operating model Compare Sprinklr and Xquik by output, cost, and handoff. Sprinklr is an enterprise platform for customer experience, care, listening, and social operations. Xquik is focused on tweet and profile records, follower exports, monitor events & webhooks, writes, monitors, and webhooks instead of a full CX suite. Scope: Broad enterprise customer experience platform. Xquik: Tweet and profile records, follower exports, monitor events & webhooks, extractions, monitors, and webhooks. Implementation: Enterprise rollout and governance model. Xquik: Account setup, dashboard tools, and API keys. Organization fit: Global social and care organizations. Xquik: Teams that need a practical X API, monitor, and export setup. ## Xquik value to test For enterprise suite comparisons, separate cross-channel governance from the exact X tasks you need: search, exports, account actions, monitors, and webhooks. Choose Xquik when those X tasks matter more than a full social care suite. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm ownership, approvals, audit needs, seat requirements, export format, and API behavior. Decide whether the task needs a broad suite or focused tweet search, profile lookup, follower exports, reply scraping & account actions. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Do not move the entire social stack first. Pick an X-only task, run it beside the existing suite, and confirm reporting and compliance needs before broad rollout. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. Verify current social suite coverage, channels, integrations, and enterprise positioning. Verify current publishing, engagement, approval, compliance, reporting, and automation features. Verify current listening coverage, benchmarking, alerts, and report export behavior. Verify current REST API access, license requirements, approvals, and data-set enablement. # Sprout Social Alternative for X Monitoring Source: https://docs.xquik.com/alternatives/sprout-social Compare Sprout Social scheduling, Smart Inbox, listening, and analytics with Xquik tweet search, follower exports, account monitors, webhooks, SDKs, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Sprout Social or Xquik fits the X part of a social media management workflow: plan posts, manage a Smart Inbox, analyze reports, search tweets, export followers, monitor accounts or keywords, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Sprout Social pricing, plan limits, Listening and Premium Analytics add-ons, Smart Inbox features, analytics exports, and product terms on the official site before buying. ## Quick answer You need a cross-channel social media management suite for publishing, Smart Inbox engagement, approval workflows, analytics, listening, customer care, and team reporting. You need the X part in code: tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, or MCP. ## Source-backed Sprout Social scope Sprout Social's official features page describes a unified Smart Inbox, Brand Keywords with keyword, hashtag, and location searches across X, Contact Views, conversation history, multi-profile publishing, multimedia publishing, ViralPost send-time optimization, message approval workflows, bulk scheduling, PDF/CSV exports, X competitor reports, X keyword reports, CRM integrations, and chatbots. Sprout's official publishing page describes a social media calendar, Optimal Send Times based on 16 weeks of audience data, AI caption and alt-text help, approval workflows, an asset library, URL tracking, and Campaign Planner workflows. Sprout's official analytics and pricing pages describe network, cross-network, paid, competitive, and internal reports, Tag Report, Post Performance Report, Competitor Reports, Case Team Activity Report, Network Reports across X and other networks, Premium Analytics, Listening, Advocacy, Influencer Marketing, and Enterprise options. Sprout's official pricing page lists annual billing and no-credit-card trial wording, Standard at USD 199 per seat/month with 5 social profiles, Professional at USD 299 per seat/month with no listed social-profile cap, Advanced at USD 399 per seat/month with the Sprout API and Helpdesk integrations, and Enterprise as custom with onboarding, SSO setup, and priority support. It also lists Premium Analytics and Listening as individual add-ons on Standard and up, Employee Advocacy and Professional Services on Standard and up, and Influencer Marketing through a contact-sales motion. ## Current cost checkpoint Use this checkpoint before comparing Sprout Social with Xquik: | Job | Sprout Social pricing signal | Xquik pricing signal | | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Manage a few social profiles with inbox work | Standard starts at USD 199 per seat/month with 5 social profiles. | Starter is USD 20/month with 140,000 credits for X tasks and API access. | | Manage many social profiles | Professional starts at USD 299 per seat/month and has no listed social-profile cap. | Xquik pricing is based on credits, active monitors, and connected X work, not social profile seats. | | Add API or helpdesk integrations | Advanced starts at USD 399 per seat/month and lists the Sprout API plus Helpdesk integrations. | Xquik includes REST authentication, endpoints, SDKs, MCP, exports, and signed webhooks. | | Add deeper reports or listening | Premium Analytics and Listening are add-ons on Standard and up. | Tweet search, follower exports, monitor events, CSV/JSON/XLSX files, and webhook delivery use the Xquik credit model. | | Add advocacy, services, or influencer workflows | Employee Advocacy and Professional Services are available on Standard and up; Influencer Marketing is contact sales. | Xquik stays focused on tweet and profile records, follower exports, monitor events & webhooks, actions, monitors, webhooks, SDKs, and MCP handoffs. | ## Comparison | Area | Sprout Social | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Marketing, support, and enterprise social teams need one workspace for multi-profile publishing, Smart Inbox engagement, approval workflows, reporting, listening, and customer care. | Teams need tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Social media management, publishing, engagement, analytics, listening, advocacy, influencer, and customer-care suite. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Sprout's official pages describe Smart Inbox, Brand Keywords for X, multi-profile publishing, approval workflows, analytics reports, listening, Premium Analytics, Advocacy, Influencer Marketing, Enterprise options, CRM integrations, and chatbots. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Check seats, social profiles, approval workflow needs, Premium Analytics, Listening, Advocacy, Influencer Marketing, Professional Services, and Enterprise requirements. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for suite setup, social profiles, inbox routing, approval workflows, report templates, listening topics, add-ons, and downstream export ownership. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Sprout Social covers cross-channel publishing, Smart Inbox engagement, analytics, listening, customer care, advocacy, and enterprise reporting. | Xquik is focused on X records, account actions, monitors, API access, signed webhooks, and export files. | | Suite breadth | Cross-channel social management, care, listening, analytics, advocacy, and influencer workflows. | Tweet search, follower exports, post actions, media uploads, DMs, monitors, SDKs, MCP, and webhooks. | | Procurement | Seat-based social platform evaluation with add-ons and enterprise scope. | Self-serve plans with API access. | | Developer fit | Social team, support team, and reporting owner first. | Developer and operator first. | ## Operating model Compare Sprout Social and Xquik by output, cost, and handoff. Sprout Social covers cross-channel publishing, Smart Inbox engagement, analytics, listening, customer care, advocacy, and enterprise reporting. Xquik is focused on X records, account actions, monitors, API access, signed webhooks, and export files. Sprout Social: social suite procurement, Smart Inbox ownership, approvals, and reporting. Xquik: self-serve tweet, profile, follower, reply & account actions with API access. Sprout Social: social profiles, inbox routing, approval workflows, reports, listening topics, and add-ons. Xquik: API keys, webhooks, SDKs, exports, and MCP. Sprout Social: reports, inbox work, social listening topics, and customer-care views. Xquik: Tweet JSON, follower CSV/XLSX, monitor events, and webhook payloads. ## Xquik value to test For social media management comparisons, separate cross-channel governance from the exact X tasks you need: search tweets, export followers, post tweets, upload media, send DMs, monitor keywords, and deliver signed webhooks. Choose Xquik when the X workflow must produce API responses, files, or signed events instead of staying inside a social suite. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: route inbox messages, schedule a campaign post, search tweets for a product term, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare ownership, approvals, inbox routing, analytics exports, tweet IDs, author IDs, timestamps, post results, CSV/JSON/XLSX exports, webhook signatures, and error handling. Compare Sprout seats, social profiles, Premium Analytics, Listening, Advocacy, Influencer Marketing, and Enterprise scope with Xquik credits, active monitor billing, top-ups, and engineering time. ## Migration path Do not move the entire social stack first. Keep Sprout Social for publishing, Smart Inbox, approvals, analytics, listening, care, advocacy, and influencer workflows if that is already the operating model. Move one X-only task when it needs tweet results, follower files, signed webhooks, or MCP tool results. Keep the test small: one task, one output, one cost model, and one downstream owner. Use Xquik for the X work that needs tweet search, follower export, post actions, media uploads, DMs, monitor tweets, webhook delivery, SDK calls, or MCP. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current publishing, Smart Inbox, reporting, X keyword report, and approval workflow features. Verify current plans, add-ons, Premium Analytics, Listening, Advocacy, and Influencer Marketing. Verify current calendar, approval, Optimal Send Times, asset, URL tracking, and campaign planning features. Verify current reports, network coverage, competitor reporting, and Premium Analytics features. # Talkwalker Alternative for Social Listening Source: https://docs.xquik.com/alternatives/talkwalker Compare Talkwalker social listening, media monitoring, and consumer intelligence with Xquik tweet search, follower exports, monitors, webhooks, APIs, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Talkwalker or Xquik fits the X part of a social listening, consumer intelligence, or social media analytics workflow: search tweets, export followers, monitor accounts or keywords, send webhooks, and hand records to apps, warehouses, or agents. This is a factual comparison and migration guide. Verify current Talkwalker pricing, access rules, and product terms on the official site before buying. ## Quick answer You need a broader consumer intelligence or social listening platform for audience sentiment, trend analysis, visual listening, competitive intelligence, and marketing insights. You need the X part in code: tweet search, follower exports, 1-second monitors, signed webhooks, SDKs, MCP, and CSV/JSON/XLSX exports. ## Source-backed Talkwalker scope Talkwalker's official social listening pages describe tracking keywords and mentions across 30 social networks, 150+ million websites, videos, images, podcasts, reviews, surveys, and support interactions. Their public pages also describe visual listening, customizable dashboards, real-time alerts, conversation clusters, sentiment analysis, AI summaries, virality maps, AI chat and answers, and customer feedback analytics. Talkwalker's official product pages describe a consumer intelligence platform with Social Listening, Social Benchmarking, Media Monitoring, Customer Feedback Analytics, and real-time social insights for smarter decisions. Their data coverage pages describe 30+ social networks, 300+ review sites, over 150 million websites, and source categories that include X, LinkedIn, YouTube, TikTok, Pinterest, Bluesky, Snapchat, Twitch, Reddit, search engines, news, and review sources. Use those official pages to verify the broad social listening and consumer intelligence side of the comparison. Use Xquik's API reference and workflow guides to verify the X-specific side: tweet search, follower exports, monitors, webhooks, exports, SDKs, and MCP. ## Comparison | Area | Talkwalker | Xquik | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Marketing, insight, brand, or research teams need social listening, audience sentiment, trend discovery, visual listening, competitive intelligence, and dashboards. | Operators and developers need X records, account actions, monitor events, exports, webhooks, SDKs, and MCP without buying a broader consumer intelligence platform. | | Product type | Consumer intelligence, social listening, and social media analytics platform. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | X task coverage | Talkwalker official pages describe X coverage, social listening, media monitoring, benchmarking, dashboards, alerts, visual listening, AI summaries, and customer feedback analytics. Verify current X access, export options, API access, and package requirements before buying. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Output to test | Listening dashboards, sentiment views, trend reports, alerts, visual analysis, audience insights, and exports. | Tweet records, user records, follower rows, monitor events, signed webhook payloads, CSV/JSON/XLSX exports, SDK calls, and MCP tool results. | | Pricing & value | Check package scope, seats, historical access, export needs, API access, onboarding, and implementation time. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for suite setup, queries, dashboards, source coverage, reports, alerts, and downstream export ownership. | Start from a dashboard task, then automate the same job through REST, webhook, SDK, export, or MCP when it needs to run repeatedly. | ## Operating model Compare Talkwalker and Xquik by the job that has to leave the dashboard. Talkwalker is useful when the team needs broad consumer intelligence and social listening across many sources. Xquik is useful when the X work needs direct records, repeatable API calls, exports, signed webhooks, or agent access. Talkwalker: consumer intelligence, social listening, sentiment, trend analysis, and visual listening. Xquik: Tweet search, follower exports, monitor tweets, and X API handoff. Talkwalker: suite setup, source coverage, queries, dashboards, reports, and alerts. Xquik: API key, dashboard tools, REST calls, SDKs, webhooks, and MCP. Talkwalker: reports, alerts, dashboards, insights, and suite exports. Xquik: Tweet JSON, follower CSV/XLSX, webhook events, SDK responses, and MCP results. ## Xquik value to test For social listening and consumer intelligence comparisons, keep the trial focused on one X workflow. Compare returned records, export format, webhook delivery, API behavior, and cost. Start with tweet search, follower exports, monitor events, signed webhook payloads, CSV/JSON/XLSX exports, SDK calls, or MCP tool results. Call [Search Tweets](/api-reference/x/search-tweets) or run an extraction to collect tweets for a keyword, account, list, community, reply thread, quote chain, or mention query. Run follower exports through dashboard tools, extraction jobs, REST endpoints, or SDKs, then hand CSV, JSON, XLSX, Markdown, or paginated JSON to the next system. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. Use 128 REST operations, 10 SDKs, MCP, API keys, and pay-per-use read endpoints when the workflow needs code, agents, or repeatable jobs. ## What to verify in a trial Pick one real job: monitor a brand account, search tweets for a campaign term, export followers, or send new matching tweets to a webhook. Compare tweet IDs, author IDs, timestamps, text, metrics, media links, pagination, export fields, webhook signatures, and error handling. Compare Talkwalker package requirements with Xquik credits, active monitor billing, top-ups, and the engineering time needed to keep the workflow running. ## Migration path Do not migrate the full consumer intelligence program first. Start with one X-only workflow and one downstream owner. 1. Use Talkwalker for broad social listening, consumer intelligence dashboards, visual analysis, sentiment, trends, and competitive intelligence if that is already the operating model. 2. Use Xquik for the X task that needs API output: tweet search, follower export, monitor tweets, webhook delivery, SDK calls, or MCP. 3. Keep both systems side by side until the Xquik output matches the fields, freshness, and handoff format the downstream system needs. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Export tweet search results to CSV, JSON, XLSX, Markdown, or paginated JSON. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. Verify current source coverage, alerts, dashboards, visual listening, and AI features. Verify current Social Listening, Social Benchmarking, Media Monitoring, and feedback analytics scope. Verify current source coverage across social networks, websites, review sites, and media sources. # Taplio Alternative for Tweet Automation | Tweet API Source: https://docs.xquik.com/alternatives/taplio Compare Taplio with Xquik for tweet automation, tweet search, follower exports, post tweets and replies, monitors, signed webhooks, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Taplio or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Taplio pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team mainly needs LinkedIn post ideas, scheduling, analytics, comments, lead lists, Auto-DM, or connection requests. You need publishing plus tweet search, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, and MCP in the same account. ## Source-backed Taplio scope Taplio's official pricing page positions Taplio as an all-in-one AI-powered tool to grow a brand on LinkedIn. It lists Starter, Growth, and Pro plans with a 7-day Pro free trial. Starter is listed at USD 39/month or USD 32/month when billed yearly. It includes 0 AI credits, 0 comment credits, 1-click post scheduling, 5M+ post ideas, a carousel builder, post analytics, extension basic features, and auto-commenting on your own posts. Growth is listed at USD 69/month or USD 49/month when billed yearly. It includes 250 AI credits, 500 comment credits, everything in Starter, hook and post generation, repurposing viral posts or content, AI copilot writing, auto-reply and commenting at scale, and smart replies. Pro is listed at USD 199/month or USD 149/month when billed yearly. It lists no AI credit cap, no comment credit cap, no AI or comment cap, a dynamic 3M+ lead database, Auto-DM for likers and commenters, mass DMs to an audience, and automated connection requests. The pricing page also lists saved posts, draft Kanban, post scheduling, monthly and daily schedules, analytics, writer collaboration, organization management, Zapier integration, contact lists, imported likers and repliers, LinkedIn extension analytics, and smart replies from LinkedIn. Taplio's FAQ says the 7-day free trial gives Pro access during the trial and then switches to the originally selected plan. It says the Taplio X Chrome extension brings Taplio into LinkedIn with instant stats, high-performing posts, trending content, and quick saves. ## Comparison | Area | Taplio | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | LinkedIn-first creators comparing X feature coverage. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | LinkedIn personal-branding, content, engagement, analytics, and lead-generation tool. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Official pricing lists LinkedIn post ideas, scheduling, carousel builder, analytics, AI credits, comment credits, smart replies, contact lists, lead database, Auto-DM, mass DMs, connection requests, extension features, and Zapier integration by plan. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Official pricing lists Starter at USD 39/month, Growth at USD 69/month, and Pro at USD 199/month, with yearly prices shown at USD 32, USD 49, and USD 149/month. Value depends on LinkedIn AI credits, comment credits, lead database access, Auto-DM, and connection-request needs. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for how the task reaches live tweet search results, follower exports, account actions & monitor events, account actions, webhooks, exports, and support tools. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Taplio focuses on LinkedIn content creation, scheduling, engagement, analytics, and lead workflows. | Xquik focuses on X with write actions, extraction, monitors, API authentication, webhooks, and MCP. | | Network focus | LinkedIn creator work. | Tweet search results, follower exports, account actions & monitor events, write, and monitor tasks. | | Content use | LinkedIn writing, scheduling, carousels, comments, leads, Auto-DM, and connection requests. | X compose, extraction, monitoring, and API calls. | | Channel fit | LinkedIn-first creators. | X creators, developers, and operators. | ## Operating model Compare Taplio and Xquik by output, cost, and handoff. Taplio focuses on LinkedIn content creation, scheduling, and relationship work. Xquik focuses on X with write actions, extraction, monitors, API keys, webhooks, and MCP. Network focus: LinkedIn creator work. Xquik: Tweet search results, follower exports, account actions & monitor events, write, and monitor tasks. Content use: LinkedIn writing, scheduling, and outreach support. Xquik: X compose, extraction, monitoring, and API calls. Channel fit: LinkedIn-first creators. Xquik: X creators, developers, and operators. ## Current cost checkpoint Use current Taplio plan limits when the job is LinkedIn content or lead engagement. Use Xquik units when the job needs tweet search results, follower exports, account actions & monitor events, exports, monitors, DMs, media uploads, webhooks, SDKs, or MCP. | Task | Taplio unit to price | Xquik unit to price | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | Write and schedule LinkedIn posts | Starter is USD 39/month or USD 32/month yearly, with post scheduling, 5M+ post ideas, carousel builder, analytics, and 0 AI credits. | Compose, refine, and score are free. Creating a tweet or reply costs 30 credits/call from a connected X account. | | Use AI writing and comments | Growth is USD 69/month or USD 49/month yearly, with 250 AI credits, 500 comment credits, hooks, post generation, content repurposing, AI copilot writing, and smart replies. | Search tweets and follower exports cost 1 credit/result. Use returned X records as source material for your own writing workflow. | | Run LinkedIn lead engagement | Pro is USD 199/month or USD 149/month yearly, with no listed AI credit cap, no listed comment credit cap, a 3M+ lead database, Auto-DM, mass DMs, and automated connection requests. | Use Xquik when the workflow needs X follower exports, media uploads, DMs, signed webhooks, SDK calls, MCP, or stored monitor events. | | Monitor accounts or keywords on X | Not Taplio's core public pricing unit; validate any X monitoring need during trial. | Active monitors check every 1 second and cost 21 credits per monitor-hour. Stored events and signed webhook delivery management are included. | | Connect backend jobs or agents | Taplio lists Zapier integration and LinkedIn extension features. Validate REST, SDK, webhook, and MCP handoff needs before buying. | REST API, signed webhooks, 10 SDKs, MCP, and CSV/JSON/XLSX/Markdown exports. | ## Xquik value to test For agent and automation comparisons, test the actual handoff: can it search tweets, fetch users, run extractions, post, monitor, and receive webhook events from one API key? Choose Xquik when one API key must handle search, user fetches, extractions, posts, monitors, and webhook events. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm agent access, API behavior, connected-account actions, export formats, webhook payloads, and the handoff to your production system. Then compare seats, hosting, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one repeated task. Validate that Xquik can run the data collection, account action, export, or webhook handoff before moving more agent or dashboard work. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Check Taplio public pricing and plan limits for current LinkedIn costs. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current plan limits, AI credits, comment credits, lead features, and trial behavior before making a final decision. # TryPost Alternative for Tweet Scheduling | Tweet API Source: https://docs.xquik.com/alternatives/trypost Compare TryPost with Xquik for tweet scheduling, tweet search, follower exports, replies, monitors, signed webhooks, REST APIs, SDKs, and MCP. See examples.
For the complete documentation index, see llms.txt.
## Distinct TryPost Fit TryPost centers on open-source scheduling, cloud or self-hosting, workspaces, and 10 social networks. It differs from creator-growth tools and direct official API access. Compare its calendar, hosting, REST, and MCP paths before choosing it. Use this guide to decide whether TryPost or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current TryPost pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team needs an open-source social scheduler, cloud or self-hosting, REST access, MCP, a content calendar, media library, and posts across 10 networks. You need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, or MCP from one product. ## Source-backed TryPost scope TryPost's official pricing page says one workspace includes all features and connects to all 10 social networks. The visible cloud plan is listed at USD 16/month per workspace when billed annually, or USD 192/year. The page shows a 20% yearly saving and a 7-day free trial. Included features are all social networks, no listed scheduled-post cap, a visual content calendar, media library, post preview for all networks, no listed team-member cap, and chat support. The supported channels listed on pricing are Instagram, Facebook, LinkedIn personal and company pages, X (Twitter), TikTok, YouTube Shorts, Pinterest, Threads, Bluesky, and Mastodon. The pricing page says each additional workspace is billed separately and gives 3 workspaces as USD 48/month. TryPost's terms describe it as an open-source social media scheduling platform available as a cloud-hosted SaaS and self-hosted open-source software. The terms say the software is released under FSL-1.1-MIT, self-hosting is available at no cost, source code can be inspected and audited, and self-hosted users maintain control of their data. The terms describe the service as scheduling and auto-publishing across multiple platforms, a drag-and-drop visual calendar, multi-platform account management, team collaboration, media library, and per-platform post preview. TryPost docs list API Reference for managing posts, signatures, and more via REST API. They also list Build with AI for connecting AI assistants through MCP. ## Comparison | Area | TryPost | Xquik | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Teams that want an open-source scheduler with REST API and MCP access. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Open-source social scheduler with cloud hosting, self-hosting, REST API, and MCP docs. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Official pages list 10 social networks, no listed scheduled-post cap, visual calendar, media library, post previews, team collaboration, REST API docs, and MCP docs. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Official pricing lists USD 16/month per workspace when billed annually, USD 192/year, a 7-day trial, and separately billed extra workspaces. Value depends on workspaces, self-hosting effort, supported social channels, and scheduler needs. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for how the task reaches live tweet search results, follower exports, account actions & monitor events, account actions, webhooks, exports, and support tools. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | TryPost is an open-source social media scheduler with cloud, self-hosting, REST API, and MCP-oriented documentation. | Xquik is hosted for X reads, writes, extractions, account monitors, signed webhooks, SDKs, MCP, and dashboard tools. | | Hosting model | Cloud SaaS or self-hosted open-source software. | Hosted X API and dashboard product with API authentication. | | Core use | Plan, schedule, and publish social posts. | Publish, extract, monitor, export, and automate X tasks. | | Developer tools | REST API and MCP setup docs. | REST API, SDKs, MCP, and signed webhooks. | ## Operating model Compare TryPost and Xquik by output, cost, and handoff. TryPost is an open-source social media scheduler with cloud, self-hosting, REST API, and MCP-oriented documentation. Xquik is hosted for X reads, writes, extractions, account monitors, signed webhooks, SDKs, MCP, and dashboard tools. Hosting model: Cloud SaaS or self-hosted open-source software. Xquik: Hosted X API and dashboard product with API authentication. Core use: Plan, schedule, and publish social posts. Xquik: Publish, extract, monitor, export, and automate X tasks. Developer tools: REST API and MCP setup docs. Xquik: REST API, SDKs, MCP, and signed webhooks. ## Current cost checkpoint Use current TryPost pricing when the job is a social content calendar across 10 networks. Use Xquik units when the job needs X search results, follower exports, media uploads, DMs, monitors, signed webhooks, SDKs, exports, or MCP. | Task | TryPost unit to price | Xquik unit to price | | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Run a cloud social scheduler | Workspace is USD 16/month when billed annually, or USD 192/year, with all features and 10 social networks included. Extra workspaces are billed separately. | Xquik Starter is USD 20/month with 140,000 included credits for tweet search results, follower exports, account actions & monitor events, write, monitor, webhook, export, SDK, and MCP workflows. | | Self-host the scheduler | TryPost terms say self-hosting is available at no cost under FSL-1.1-MIT. Price hosting, updates, queues, storage, email, and social app setup. | Xquik is hosted. Use REST, SDKs, webhooks, exports, MCP, and dashboard tools without running scheduler infrastructure. | | Schedule posts across many social networks | TryPost lists Instagram, Facebook, LinkedIn, X, TikTok, YouTube Shorts, Pinterest, Threads, Bluesky, and Mastodon. | Use Xquik when the task is specifically tweet search results, follower exports, account actions & monitor events, connected-account writes, DMs, media upload, monitors, webhooks, exports, SDKs, or MCP. | | Use REST or MCP for social scheduling | TryPost docs list REST API reference for posts and signatures, plus MCP docs for AI assistants. | Xquik REST API, signed webhooks, 10 SDKs, MCP, and CSV/JSON/XLSX/Markdown exports focus on X workflows. | | Search tweets or export followers | Not a TryPost core public pricing unit; validate source-data and export needs before using it as the automation layer. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | | Monitor X accounts or keywords | TryPost focuses on scheduling and publishing. Validate alerting needs separately. | Active monitors check every 1 second and cost 21 credits per monitor-hour. Stored events and signed webhook delivery management are included. | ## Connect a TryPost Schedule to Xquik Reads Keep TryPost's editorial schedule separate from X engagement collection. Store the TryPost workspace, social account, scheduled post ID, and planned time. After publication, add the returned X post URL and Tweet ID. Use Xquik to verify the public tweet after TryPost reports publication. Fetch the Tweet ID, text, author, media, reply count, repost count, and like count. Do not treat a scheduler success state as engagement evidence. Collect replies, quotes, reposts, and likes through their specific Xquik routes. Store each collection time beside the scheduled post ID. This creates one trace from editorial approval through public engagement. Keep the TryPost workspace, target network, scheduled post ID, planned time, and reviewer. These fields prove what the team approved. Resolve the published Tweet ID. Fetch its text, author, media, and public URL. Flag missing or changed fields for review. Export replies, quotes, reposts, and likes after publication. Keep the Tweet ID and collection time with every file. Start an account or keyword monitor when alerts must outlive the schedule. Deliver matched tweets through signed webhooks. ## Verify a Cloud or Self-Hosted TryPost Trial Test either one cloud workspace or one self-hosted deployment. Record its owner, backup path, update process, and connected social accounts. Schedule one text post and one media post. Keep each TryPost post ID, planned time, destination account, caption, and media file. Store each returned X post URL and Tweet ID. Fetch both tweets through Xquik. Compare text, author, media, and publication time. Use a safe test account. Exercise one failed schedule, disconnected account, or rejected media case. Record ownership and recovery time. For cloud, price every workspace. For self-hosting, price compute, storage, backups, updates, queues, email, and social app maintenance. ## Migration path Do not migrate ten-network scheduling into an X-only workflow. Keep TryPost when its calendar, workspaces, and multi-network publishing remain required. Add Xquik where the X workflow needs tweets, followers, replies, or monitors. Start with one scheduled X post. Preserve its TryPost post ID and publish time. After publication, attach the Tweet ID and Xquik verification result. Then collect one reply or engagement export. Assign each responsibility before expanding. TryPost owns the editorial calendar and cross-network schedule. Xquik owns the X read, export, monitor, and webhook steps selected by the team. Measure the full monthly cost. Include TryPost workspaces or self-hosting. Add Xquik credits only for the X calls and active monitors used. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current workspace pricing, included networks, self-hosting terms, REST docs, and MCP docs before making a final decision. # Tweet Hunter Alternative for Scheduling & Analytics Source: https://docs.xquik.com/alternatives/tweet-hunter Compare Tweet Hunter AI writing, scheduling, analytics, Auto-DMs, and creator CRM with Xquik tweet search, follower exports, monitors, webhooks, SDKs, and MCP.
For the complete documentation index, see llms.txt.
## Distinct Tweet Hunter Fit Tweet Hunter centers on creator growth, AI writing, scheduling, Auto-DMs, analytics, and CRM. It differs from profile analytics, automation feeds, and real-time tweet and follower services. Choose this comparison when the output stays inside a creator workflow. Use this guide to decide whether Tweet Hunter or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This guide uses the public Tweet Hunter pricing page as the competitor source. Re-check it before buying because offers, account limits, and Auto-DM limits can change. ## Quick answer You need a creator dashboard for tweet ideas, AI writing, scheduling, X analytics, Auto-DMs, and creator CRM work. You need tweet search, follower exports, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, MCP, and per-call pricing. ## Source-backed Tweet Hunter scope Tweet Hunter's official pricing page describes it as an all-in-one AI X tool for growing an audience and brand on X. The visible offer lists Discover, Grow, and Enterprise plans with a 7-day trial. Discover is listed at USD 29/month under the visible offer and includes 1 X account, an over 12M viral tweets library, custom tweet inspirations, faster engagement, tweet and thread scheduling, evergreen tweets, 3,000 Auto-DMs/month, auto-plug, auto-retweet, and complete X analytics. Grow is listed at USD 49/month under the visible offer and includes 5 X accounts, everything in Discover, 7,500 Auto-DMs/month, paid partnership labels on posts, daily AI-written tweets, tweet rewrites, finish-a-tweet help, thread ideas and hooks, X CRM lists, imports from previous tweet and DM interactions, and engagement with tweets from specific lists. Enterprise is displayed at roughly USD 199/month in the visible offer and includes everything in Grow, 15,000 Auto-DMs/month, ghostwriting mode, priority support, custom trained AI, smart AI reply generation, and broad AI use. Tweet Hunter's official Advanced Options help page says Auto-DM can send a Direct Message based on likes, replies, or retweets. It also says Auto-DM works for the first 72 hours of a tweet, checks new engagements every minute during the first 24 hours, has a 500 DMs/tweet cap, and requires transparent wording that tells people they will receive a DM when they interact. ## Comparison | Area | Tweet Hunter | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Creators need tweet ideas, AI writing, scheduling, analytics, Auto-DMs, and simple X CRM. | Teams need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Creator growth tool. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Public pricing lists a 12M+ viral tweet library, custom inspirations, scheduling, evergreen tweets, analytics, Auto-DMs, auto-plug, auto-retweet, AI writer, and CRM features by plan. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | The current public pricing page lists Discover at USD 29/month for 1 X account, Grow at USD 49/month for 5 X accounts, and Enterprise around USD 199/month under its displayed offer. Verify the live page before buying because the offer is presented as limited-time. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit. Webhook and stored-event management are free. | | Integration effort | Use it when the output stays inside the creator workflow: draft, schedule, analyze, Auto-DM, or manage creator contacts. | Use a dashboard tool first, then move the same task to REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Tweet Hunter fits content creation and audience growth inside its dashboard. | Xquik fits reusable tweet search, follower exports, media posts & account monitors, exports, alerts, write actions, or an API handoff. | | Content support | Tweet ideas, AI-written tweets, rewrites, hooks, scheduling, evergreen tweets, and analytics. | Free compose, refine, and score steps, then metered create-tweet and media-upload actions for connected accounts. | | Monitoring | Analytics, engagement lists, Auto-DM triggers, and creator growth tracking. | Account and keyword monitors check every 1 second and can deliver signed webhooks. | | Developer fit | Creator dashboard first. Public pricing does not position it as an API platform for exports, SDKs, or MCP. | Dashboard, REST API, signed webhooks, CSV/JSON/XLSX/Markdown exports, 10 SDKs, and MCP. | ## Operating model Compare Tweet Hunter and Xquik by output, cost, and handoff. Tweet Hunter focuses on creator output: ideas, writing, scheduling, analytics, Auto-DMs, and CRM. Xquik focuses on reusable X records: tweets, users, followers, media, DMs, monitor events, webhook payloads, exports, SDK calls, and MCP tool calls. Tweet Hunter: create and schedule content. Xquik: search tweets, export followers and replies, run writes, monitor accounts, and notify other systems. Tweet Hunter: creator account setup. Xquik: API key, connected X account for writes, optional webhook endpoint, and SDK or MCP setup. Tweet Hunter: scheduled posts and creator analytics. Xquik: API responses, exports, stored monitor events, signed webhooks, and agent tools. ## Cost Checkpoints | Task | Tweet Hunter pricing unit | Xquik pricing unit | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Draft and schedule tweets | Subscription plan. The public page currently shows Discover at USD 29/month and Grow at USD 49/month. | Compose, refine, and score are free. Creating a tweet or reply costs 30 credits. | | Upload media for posts or DMs | Included only as product capability where available in the plan. Verify limits on the live page. | Upload media costs 10 credits and returns `mediaUrl` or `mediaId` for the next action. | | Run Auto-DM campaigns | Discover lists 3,000 Auto-DMs/month, Grow lists 7,500 Auto-DMs/month, and Enterprise lists 15,000 Auto-DMs/month. The help page says each Auto-DM tweet works for 72 hours and has a 500 DMs/tweet cap. | Send DM costs 10 credits/call. Use Xquik when the same workflow needs media upload, follower export, webhook delivery, SDK calls, or event storage. | | Search tweets or export followers | Not the core public pricing unit. Validate export needs during trial. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | | Monitor accounts or keywords | Analytics and growth tracking in the creator dashboard. | Active monitors cost 21 credits per hour, check every 1 second, and include stored events plus webhook delivery. | | Connect agents and backend jobs | Dashboard-first workflow. Verify any automation handoff before buying. | REST API, signed webhooks, 10 SDKs, MCP, and CSV/JSON/XLSX/Markdown exports. | ## Xquik Value To Test For publishing comparisons, test what happens before and after the post: find source tweets, upload media, send DMs, export replies, monitor keywords, and notify downstream tools. Choose Xquik when publishing must include tweet, reply, follower, or monitor outputs. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm draft flow, media handling, scheduling needs, export formats, API access, and whether tweet search, follower exports, or monitoring matter after publishing. Then compare seats, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one task that has a measurable output. Good tests are a 1,000-result tweet search, a follower export, a media-backed post, a DM send, or a keyword monitor that sends signed webhooks. Keep the content calendar stable while you test Xquik for tweet search, follower exports, and automation. Switch work that needs replies, profile records, alerts, API calls, or lower per-result cost. Check Tweet Hunter public pricing next to Xquik credits for current totals. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current plans, account limits, Auto-DM limits, and AI features before making a final decision. # TweetDeck Alternative for Tweet Monitoring Source: https://docs.xquik.com/alternatives/tweetdeck Compare TweetDeck/X Pro with Xquik for live columns, advanced search, scheduled posts, tweet search, follower exports, monitor tweets, webhooks, API, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether TweetDeck, now X Pro, or Xquik fits the X job: monitor live columns, search X, compose or schedule posts, export followers, run tweet search, deliver signed webhooks, or hand records to apps and agents. This is a factual comparison and migration guide. Verify current X Pro access rules in the official [X Pro help page](https://help.x.com/en/using-x/x-pro) and [X Premium page](https://help.x.com/en/using-x/x-premium) before buying. ## Quick answer Your team needs the official X multi-column interface, advanced search, post composer, scheduled posts, Decks, and account switching in a browser. You need tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, or MCP from one product. ## Source-backed TweetDeck/X Pro scope X's official X Pro help describes X Pro as the global replacement for TweetDeck, with a multi-column workspace that incorporates more of X.com. The page lists a full post composer, scheduled posts, advanced search, top/latest post order, Decks, a column creator with Search X, video docking, and account switching. The official X Pro usage guide describes X Pro as a browser workspace for viewing multiple timelines in one interface. It covers connecting multiple X accounts, creating posts with media, adding columns, reordering columns, and column types for home, notifications, search, lists, communities, explore, bookmarks, profiles, messages, and scheduled posts. The official X Premium help page describes X Premium as an optional paid subscription with Basic, Premium, and Premium+ tiers. It says Premium features are subject to change, new subscriptions require a verified phone number, and regional price information is shown on web and in-app. The official X Pro FAQ says X Pro lets teams delegate account access without sharing sign-in credentials. It also says X Pro does not support scheduled Direct Messages. ## Comparison | Area | TweetDeck | Xquik | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Operators want multiple configurable timelines, advanced search, column groups, account switching, and scheduled posts in the official X dashboard. | Teams need tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Official X multi-column dashboard, formerly TweetDeck. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | X Pro help lists a full post composer, scheduled posts, advanced search, top/latest post order, Decks, a column creator, video docking, and account switching. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Verify the required X Premium tier, regional price, account eligibility, and product availability. X says Premium features can change. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for browser-based columns, saved searches, lists, profile columns, account switching, and manual review. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | TweetDeck/X Pro is a live X workspace for columns, search, scheduled posts, and manual monitoring. | Xquik is built for automation, exports, monitors, webhooks, and API calls. | | Interface | Browser columns, Decks, advanced search, and post composer. | Tools dashboard plus API calls. | | Data movement | Manual monitoring, saved columns, X account data download, and dashboard review. | Exports, monitors, API responses, and webhook events. | | Automation | Native X dashboard workflow. | Automated extraction, write actions, and monitoring. | ## Operating model Compare TweetDeck/X Pro and Xquik by output, cost, and handoff. TweetDeck/X Pro is a live X workspace for columns, search, scheduled posts, and manual monitoring. Xquik is built for automation, exports, monitors, webhooks, and API calls. Interface: Browser columns, Decks, advanced search, and post composer. Xquik: tools dashboard plus API calls. Data movement: manual monitoring, saved columns, and dashboard review. Xquik: exports, monitors, API responses, and webhook events. Automation: native X dashboard workflow. Xquik: automated extraction, write actions, and monitoring. ## Xquik value to test For TweetDeck/X Pro comparisons, test the actual handoff: can the workflow search tweets, fetch users, export followers, post tweets, upload media, send DMs, monitor keywords, and receive webhook events from one API key? Choose Xquik when one API key must handle search, user fetches, extractions, posts, monitors, and webhook events. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: monitor a brand account, search a keyword, export followers, upload media, send a DM, post a tweet, or deliver a webhook. Compare outputs, not feature labels. Compare columns, saved searches, tweet IDs, author IDs, timestamps, post results, export formats, webhook payloads, and the handoff to your production system. Compare the X Pro subscription requirement with Xquik credits. Active Xquik monitors cost 21 credits per hour while enabled, and webhook plus stored-event delivery is included. ## Migration path Start with one repeated task. Validate that Xquik can run the data collection, account action, export, or webhook handoff before moving more dashboard work. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. X Pro availability and pricing are governed by X public subscription terms. X's public Premium docs describe X Pro as a Premium+ feature and say features may change, so verify the current tier before relying on it for a team workflow. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current X Pro features, columns, Decks, composer, search, and account-switching details. Verify current subscription tier, regional pricing, and feature availability before making a final decision. # TweetStream Alternative for Crypto Tweet Alerts Source: https://docs.xquik.com/alternatives/tweetstream Compare TweetStream WebSocket crypto tweet alerts, OCR, enrichment, and replay with Xquik tweet search, follower exports, monitors, webhooks, SDKs, and MCP.
For the complete documentation index, see llms.txt.
Use this guide to decide whether TweetStream or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current TweetStream pricing, access rules, and product terms on the official site before buying. ## Quick answer Your team needs monitored-account WebSocket alerts for trading workflows, crypto asset detection, OCR, replay, and Discord delivery. You need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, or MCP from one product. ## Source-backed TweetStream scope TweetStream's official home describes it as a Twitter WebSocket API for crypto traders that streams structured JSON for tracked accounts. The home page says teams connect a live feed, track accounts with keyword filters, and receive alerts that can include OCR text, detected crypto assets, live prices, and Polymarket detection. TweetStream's pricing surface shows a Basic annual plan at USD 139/month billed annually with 3 WebSocket connections and 50 monitored X/Twitter accounts, and an Elite annual plan at USD 349/month billed annually with 10 WebSocket connections and 250 monitored accounts. Other TweetStream docs and guides describe monthly Basic pricing from USD 199/month and Elite pricing at USD 499/month, so validate monthly and annual pricing at checkout before buying. The quickstart lists the WebSocket endpoint as `wss://ws.tweetstream.io/ws`, protocol `tweetstream.v1`, and an auth subprotocol. It also says Bearer auth headers and query parameters are accepted when an environment cannot set the auth subprotocol. TweetStream's WebSocket envelope uses `v`, `t`, `op`, `ts`, and `d`. The message family `t` can be `tweet`, `account`, or `control`, and operations include `content`, `meta`, `update`, `delete`, `profile_update`, `follow`, `auth_ping`, `auth_pong`, and `twitter_handles_result`. Payload docs say tweet events send a `content` message first, optional `update` messages, and a `meta` message when enrichment is ready. The tweet payload includes `tweetId`, `text`, `createdAt`, `author`, optional `link`, optional `media`, and optional `ref`. TweetStream account events include profile updates and follow notifications. Profile changes can include avatar, banner, bio, handle, location, and name. The `meta` payload can include OCR text and detected crypto assets, centralized-exchange markets, and prediction markets, with source markers for text or OCR. The history API returns rows with `tweetId`, `body`, `time`, `receivedTime`, `link`, `messageType`, `twitterHandle`, `twitterId`, `content`, and optional `meta`. ## Comparison | Area | TweetStream | Xquik | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Crypto desks and bots that need a real-time tweet stream. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Monitored-account X/Twitter WebSocket alert service for trading workflows. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | WebSocket tweet events, account events, OCR text, detected crypto assets, market enrichment, history replay, and Discord delivery on supported plans. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Public surfaces show annual Basic at USD 139/month, annual Elite at USD 349/month, and separate monthly references from USD 199/month. Validate the billing interval before buying. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for WebSocket auth, reconnects, content/meta/update envelopes, account events, history replay, and downstream trading or alert routing. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | TweetStream fits monitored-account alert streams for crypto trading teams that need WebSocket delivery and enrichment. | Xquik covers more X tasks: search, followers, article reads, monitors, webhooks, extractions, SDKs, and MCP. | | Data shape | WebSocket envelopes, enrichment metadata, account events, and replay rows. | REST responses, extractions, exports, and webhook events. | | Audience | Crypto traders, bots, and research teams. | Creators, developers, growth teams, and operators. | | Scope | Monitored-account alerts, profile and follow events, media OCR, detected assets, market context, and replay. | Publishing, search, account data, followers, monitors, draws, and exports. | ## Operating model Compare TweetStream and Xquik by output, cost, and handoff. TweetStream fits monitored-account alert streams for crypto trading teams that need WebSocket delivery and enrichment. Xquik covers more X tasks: search, followers, article reads, monitors, webhooks, extractions, SDKs, and MCP. Data shape: WebSocket envelopes, enrichment metadata, account events, and replay rows. Xquik: REST responses, extractions, exports, and webhook events. Audience: Crypto traders, bots, and research teams. Xquik: Creators, developers, growth teams, and operators. Scope: Monitored-account alerts, profile and follow events, media OCR, detected assets, market context, and replay. Xquik: Publishing, search, account data, followers, monitors, draws, and exports. ## Current cost checkpoint Use TweetStream units when the job is monitored-account trading alerts over WebSocket. Use Xquik units when the job needs X search, follower exports, write actions, media upload, DMs, monitors, signed webhooks, SDKs, exports, or MCP. | Task | TweetStream unit to price | Xquik unit to price | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Stream tracked-account alerts | Basic annual plan lists 50 monitored accounts and 3 WebSocket connections; Elite annual plan lists 250 monitored accounts and 10 WebSocket connections. | Use Xquik monitors when the job needs X account or keyword events with stored events, signed webhooks, REST polling, or exports. | | Budget monthly access | Public TweetStream surfaces show annual prices and separate monthly references; validate the billing interval and account count before buying. | Xquik Starter is USD 20/month with 140,000 credits, and many read calls cost 1 credit/result or call. | | Parse live data | WebSocket envelopes use `v`, `t`, `op`, `ts`, and `d`; tweet events split content, optional updates, and enrichment metadata. | Xquik returns REST responses, extraction rows, export files, monitor events, and webhook payloads. | | Enrich trading signals | TweetStream docs cover OCR text, detected crypto assets, market context, account profile updates, follow events, and replay rows. | Use Xquik when the workflow also needs tweet search, follower exports, media upload, DMs, posting, SDKs, or MCP tools. | | Search tweets or export followers | TweetStream's public docs focus on monitored-account WebSocket alerts and replay. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | ## Xquik value to test For API comparisons, price the whole task: endpoint access, pagination, retries, storage, exports, alerts, webhooks, SDKs, and maintenance. Choose Xquik when those pieces must ship together. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm returned data, pagination, retry behavior, webhook payloads, export formats, and API ergonomics. Then compare total cost for the same task: access, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one API-backed task. Keep the current integration running while you compare output shape, latency, pagination, retries, exports, and alert delivery in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Compare TweetStream and Xquik by stream latency, enrichment needs, REST endpoint breadth, and export needs. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. # Twitter API Pro Alternative for Tweet Search Source: https://docs.xquik.com/alternatives/twitter-api-pro Compare legacy Twitter API Pro pricing, credentials, rate limits, and pagination with Xquik tweet search, follower exports, monitors, webhooks, SDKs, and MCP.
For the complete documentation index, see llms.txt.
## Distinct Twitter API Pro Fit This page treats Twitter API Pro as a legacy comparison term. It focuses on direct official API access, credentials, pagination, and usage billing. Use the broader X API page for the current baseline. Use this guide to decide whether Twitter API Pro or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. "Twitter API Pro" is a legacy comparison term. Verify current X API pricing, access rules, and product terms on the [official pricing page](https://docs.x.com/x-api/getting-started/pricing) before buying. ## Quick answer You already have approved developer access, platform terms match the project, and your team wants direct control over endpoint design. You want ready endpoints for tweet search, user lookup, followers, writes, exports, monitors, and signed webhooks without building the surrounding tooling. ## Source-backed Twitter API Pro scope The current official X docs present X API v2 as the recommended API version for new projects. Treat "Twitter API Pro" as a legacy comparison term, then compare the workload against current X API pay-per-use docs before buying or migrating. The official X API overview describes programmatic access for reading posts, publishing content, managing users, analyzing metrics and trends, sending DMs, managing lists, and working with Spaces. It also describes fields, expansions, annotations, conversation tracking, edit history, and pay-per-usage pricing as v2 highlights. The official Getting Access docs describe the setup path as creating a developer account, creating an app, and saving credentials. They describe Bearer Token access for reading public data and OAuth user-context access for posting, liking, following, and accessing DMs. The official Search Posts docs list recent search at `GET /2/tweets/search/recent` for the last 7 days and full-archive search at `GET /2/tweets/search/all` for the complete archive. Recent search supports up to 100 posts per request, while full-archive search supports up to 500 posts per request for pay-per-use and Enterprise customers. The official pricing docs describe credits purchased upfront, current rates in the Developer Console, reads charged per resource, writes charged per request, Owned Reads at USD 0.001 per resource for eligible app-owned data, and 24-hour UTC deduplication for billable resources. The official pagination docs describe the integration loop for large exports: request `max_results`, read the next-page cursor from response metadata, send that cursor on the next request, and repeat until no cursor remains. ## Comparison | Area | Twitter API Pro | Xquik | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Teams comparing higher-tier official API access with packaged X tools. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Official X API access. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Usually covers one area: raw API access, stream delivery, publishing calls, or self-hosted collection code. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Current X API docs present pay-per-usage. Official docs list post reads at USD 0.005/resource, follower reads at USD 0.010/resource, and content creates at USD 0.015/request. Check the Developer Console for current rates. | Xquik starts at USD 20/month with 140,000 included credits. Tweet search and follower exports cost 1 credit/result. Top-up credits cost USD 0.00015 each. Seven direct MPP operations cost USD 0.00015 to USD 0.00075 per call. | | Integration effort | Plan for auth, pagination, retries, storage, exports, alerts, and operational tooling. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Twitter API Pro is a legacy name for higher-tier official X API access. | Xquik packages tweet search, profile lookup, follower and reply exports, timelines, account actions, and monitors into plans, dashboard tools, and API calls. | | Buying motion | Official developer plan evaluation. | Self-serve SaaS subscription and API keys. | | User experience | API-first implementation. | Dashboard-first with API and MCP available. | | Direct access | Teams that require direct official API access. | Teams that want ready endpoints and portable exports. | ## Operating model Compare Twitter API Pro and Xquik by output, cost, and handoff. Twitter API Pro is a legacy name for higher-tier official X API access. Xquik packages tweet search, profile lookup, follower and reply exports, timelines, account actions, and monitors into plans, dashboard tools, and API calls. Buying motion: Official developer plan evaluation. Xquik: Self-serve SaaS subscription and API keys. User experience: API-first implementation. Xquik: Dashboard-first with API and MCP available. Direct access: Teams that require direct official API access. Xquik: Teams that want ready endpoints and portable exports. ## Current cost checkpoint Use the current X API pay-per-usage model when comparing older Twitter API Pro notes. Price the exact unit you need before estimating migration cost. | Task | Current X API unit to price | Xquik unit to price | | ---------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Search 1,000 posts | Posts read: USD 0.005 per resource in the official pricing docs. | Search tweets: 1 credit per tweet. Top-up credits cost USD 0.00015 each; included subscription credits can lower the effective per-credit rate. | | Export 1,000 followers | Following/follower read: USD 0.010 per resource in the official pricing docs. | Followers endpoint: 1 credit per user. Top-up credits cost USD 0.00015 each; CSV, JSON, XLSX, Markdown, and API outputs are ready in Xquik. | | Publish 100 posts | Content create: USD 0.015 per request, or USD 0.200 per request with URL in the official pricing docs. | Create tweet or reply: 30 credits per call. Upload media separately when needed, also 10 credits per call. | | Monitor accounts | Price stream or polling access, storage, retries, alerting, and webhook delivery. | Active monitors check every 1 second and cost 21 credits per monitor-hour. Stored events and webhook delivery management are included. | Use current X API access when direct platform access is the requirement. Use Xquik when the buyer needs a packaged output: export file, webhook, SDK call, MCP tool, or dashboard workflow. ## Xquik value to test For API comparisons, price the whole task: endpoint access, pagination, retries, storage, exports, alerts, webhooks, SDKs, and maintenance. Choose Xquik when those pieces must ship together. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm returned data, pagination, retry behavior, webhook payloads, export formats, and API ergonomics. Then compare total cost for the same task: access, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one API-backed task. Keep the current integration running while you compare output shape, latency, pagination, retries, exports, and alert delivery in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Verify official X API pricing directly because terms can change without notice. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. On X API, use the official per-resource and per-request prices in the Developer Console, then add any engineering time for storage, exports, retries, dashboards, SDKs, and webhooks. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current X API pay-per-usage rates before making a final decision. # twscrape Alternative for Tweet Scraping | Tweet API Source: https://docs.xquik.com/alternatives/twscrape Compare twscrape with Xquik for tweet scraping, follower exports, replies, profiles, timelines, monitors, signed webhooks, REST APIs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether twscrape or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current twscrape pricing, access rules, and product terms on the official site before buying. ## Quick answer You want a Python library or CLI, can supply authorized X/Twitter accounts, and can maintain sessions, rate limits, storage, and exports yourself. You need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, or MCP from one product. ## Source-backed twscrape scope twscrape's official GitHub README describes it as a Twitter GraphQL API implementation with SNScrape data models. Installation uses `pip install twscrape`, and development installs can point at the GitHub repository. The README lists support for Search and GraphQL Twitter APIs, async/await functions that can run multiple scrapers in parallel, login flow with email verification, saved account sessions, raw Twitter API responses, SNScrape models, and automatic account switching for rate-limit smoothing. The README says twscrape requires authorized X/Twitter accounts to work with the API. It documents adding accounts with cookies or login credentials, login and relogin CLI commands, account status inspection, account database selection, and optional manual email verification. The usage examples cover search tabs for Top, Latest, and Media; tweet details; retweeters; tweet replies; user lookup by login or ID; following; followers; verified followers; subscriptions; user tweets; user replies; user media; list timelines; trends; raw responses; and converting Tweet/User models to dict or JSON. The CLI docs cover search, tweet details, replies, retweeters, user lookup, user media, following, followers, verified followers, subscriptions, user timelines, trends, one-document-per-line stdout output, raw output, desired `limit`, and per-endpoint pagination behavior. The README limitations say request limits reset every 15 minutes per endpoint, each account has separate limits by operation, `user_tweets` and `user_tweets_and_replies` can return about 3,200 tweets maximum, and rate limits may vary by account age and status. ## Comparison | Area | twscrape | Xquik | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Developers comfortable maintaining their own scraping code. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Open-source library. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | twscrape README covers Search and GraphQL access, account sessions, raw or SNScrape-shaped responses, search/tweet/user/list/trend calls, and account switching for rate-limit smoothing. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | The package is open source and can be free to install, but you own authorized account access, session upkeep, rate limits, output files, and job operations. | Xquik starts at USD 20/month with 140,000 included credits. Many read calls cost 1 credit per result or call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for Python setup, account pool setup, login or cookie handling, CLI output, pagination behavior, retries, storage, exports, alerts, and monitoring. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | twscrape is an open-source library for developers who want to build and maintain their own tweet, profile, follower, reply & timeline pipeline. | Xquik gives teams hosted API keys, exports, monitoring, support, and billing. | | Ownership | Self-hosted library and maintenance burden. | Hosted product with dashboard and API. | | Operational work | You own retries, storage, exports, and uptime. | Xquik handles exports, uptime, and support. | | Ownership fit | Teams that want full code ownership. | Teams that want reliable hosted endpoints without building them first. | ## Operating model Compare twscrape and Xquik by output, cost, and handoff. twscrape is an open-source library for developers who want to build and maintain their own tweet, profile, follower, reply & timeline pipeline. Xquik gives teams hosted API keys, exports, monitoring, support, and billing. Ownership: Self-hosted library and maintenance burden. Xquik: Hosted product with dashboard and API. Operational work: You own retries, storage, exports, and uptime. Xquik: Xquik handles exports, uptime, and support. Ownership fit: Teams that want full code ownership. Xquik: Teams that want reliable hosted endpoints without building them first. ## Xquik value to test For API comparisons, price the whole task: endpoint access, pagination, retries, storage, exports, alerts, webhooks, SDKs, and maintenance. Choose Xquik when those pieces must ship together. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm returned data, pagination, retry behavior, webhook payloads, export formats, and API ergonomics. Then compare total cost for the same task: access, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one API-backed task. Keep the current integration running while you compare output shape, latency, pagination, retries, exports, and alert delivery in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Open-source software may be free to use, but operations, maintenance, and reliability remain your responsibility. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current product details before making a final decision. # Typefully Alternative for Tweet Threads | Tweet API Source: https://docs.xquik.com/alternatives/typefully Compare Typefully with Xquik for thread scheduling, tweet search, follower exports, post tweets, media uploads, monitor tweets, webhooks, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Typefully or Xquik fits a creator publishing workflow: write X threads, schedule posts, cross-post content, use AI writing help, inspect analytics, search tweets, export followers, monitor accounts or keywords, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Typefully pricing, API access, MCP access, supported platforms, analytics, cross-posting, Auto-DM, and product terms on the official site before buying. ## Quick answer You mainly need a creator workspace for drafting X threads, scheduling posts, cross-posting to social platforms, collaborating on drafts, and reviewing analytics. You need the X part in code: tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, or MCP. ## Source-backed Typefully scope Typefully's official X scheduling page describes AI writing help, X post and thread scheduling, natural-language scheduling, predefined time slots, content calendar, cross-posting to LinkedIn, Threads, Bluesky, and other platforms, detailed X analytics, multiple X accounts, team collaboration, text posts, threads, long-form posts, images, videos, GIFs, polls, auto-splitting long-form posts into threads, realistic previews, and auto-plugs. Typefully's API and MCP docs describe a REST API for creating, editing, scheduling, publishing, and deleting drafts; posting to X, LinkedIn, Threads, Bluesky, and Mastodon; media uploads for images, videos, GIFs, and PDFs; tags; social sets; draft comments; webhooks for draft created, published, scheduled, status changed, tags changed, and deleted events; development mode for IDs; AI-agent workflows; and MCP actions for drafts, queue, published posts, media uploads, tags, and connected accounts. Typefully's scheduling, analytics, and Auto-DM help pages describe saved schedule slots, suggested X posting times, Natural Posting Times with up to 4 minutes of variation, weekly and monthly calendar views, drag-and-drop rescheduling, tag filters, minimum calendar gaps, X-only analytics with impressions, engagements, engagement rate, profile conversion rate, CSV exports, Auto-DM triggers for replies, retweets, or follows, 3 simultaneous Auto-DM campaigns, 30 DMs/minute, 100 DMs/hour, 500 DMs/day, 4-day Auto-DM duration, and sent, queued, and failed DM statuses. ## Comparison | Area | Typefully | Xquik | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Creator teams need an AI-assisted writing, thread scheduling, cross-posting, draft review, and analytics workspace. | Teams need tweet search, follower exports, post tweets, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Creator publishing, X thread scheduling, social scheduling, analytics, API, and MCP platform. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Typefully's official pages describe AI writing, X thread scheduling, content calendars, saved slots, suggested times, cross-posting, X analytics, Auto-DMs, draft comments, social sets, media uploads, REST API, webhooks, MCP, and agent skills. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Check plan limits for drafts, connected accounts, team collaboration, analytics, API access, MCP access, Auto-DMs, and cross-posting. | Xquik starts at USD 20/month with 140,000 included credits. Tweet search and follower exports cost 1 credit/result. Common X write calls cost 10 credits/call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Plan for Typefully social sets, draft IDs, media uploads, tags, draft comments, published-post webhooks, and AI assistant or MCP setup. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Typefully helps creators write, schedule, cross-post, review, and measure social posts. | Xquik is focused on X records, account actions, monitors, API access, signed webhooks, and export files. | | Primary use | Creator publishing workspace for X threads and cross-posted content. | Automation platform for X publishing, data, monitoring, exports, and API access. | | Developer tools | Typefully REST API, draft webhooks, and Typefully MCP for content workflows. | REST API, webhooks, dashboard tools, SDKs, and MCP server for X workflows. | | Data export | Content operations, queue, drafts, published posts, analytics, and webhook events. | CSV, JSON, XLSX, Markdown, webhook events, SDK responses, MCP results, and API responses. | ## Operating model Compare Typefully and Xquik by output, cost, and handoff. Typefully helps creators write, schedule, cross-post, review, and measure social posts. Xquik is focused on X records, account actions, monitors, API access, signed webhooks, and export files. Typefully: X thread writing, queue scheduling, cross-posting, draft review, and analytics. Xquik: self-serve tweet, profile, follower, reply & account actions with API access. Typefully: social sets, drafts, media uploads, tags, draft comments, webhooks, and MCP. Xquik: API keys, webhooks, SDKs, exports, and MCP. Typefully: scheduled posts, draft workflows, analytics, published-post webhooks, and MCP content actions. Xquik: Tweet JSON, follower CSV/XLSX, monitor events, and webhook payloads. ## Xquik value to test For creator publishing comparisons, test what happens before and after the post: search source tweets, post tweets, upload media, send DMs, export replies, monitor keywords, deliver signed webhooks, and hand records to downstream tools. Choose Xquik when publishing must include API records, export files, monitor events, or signed webhooks. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: schedule an X thread, cross-post it, create a draft through an API, upload media, search tweets for a campaign term, export followers, send a DM, monitor an account, or deliver a webhook. Compare draft IDs, social sets, media IDs, queue state, analytics fields, tweet IDs, author IDs, timestamps, post results, CSV/JSON/XLSX exports, webhook signatures, and error handling. Compare Typefully connected accounts, collaboration, analytics, API access, MCP access, Auto-DMs, and cross-posting with Xquik credits, active monitor billing, top-ups, and engineering time. ## Migration path Do not move the whole creator calendar first. Keep Typefully for writing, X thread scheduling, cross-posting, draft comments, analytics, Auto-DMs, and content workflow automation if that is already the operating model. Move one X-only task when the output must become API data, files, signed webhooks, or MCP tool results. Keep the test small: one task, one output, one cost model, and one downstream owner. Use Xquik for the X work that needs tweet search, follower export, post actions, media uploads, DMs, monitor tweets, webhook delivery, SDK calls, or MCP. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current X scheduling, AI writing, cross-posting, analytics, media, and thread features. Verify current REST API, draft, media upload, social set, comment, and webhook support. Verify current MCP, AI assistant, queue, draft, media upload, tag, and connected-account support. Verify current schedule slots, suggested times, calendar, tag filters, and queue behavior. Verify current X analytics metrics, refresh timing, and CSV export behavior. Verify current Auto-DM triggers, limits, duration, and delivery statuses. # Xquik Vs X API V2: Twitter API Alternative Guide Source: https://docs.xquik.com/alternatives/x-api Compare X API and Twitter API alternatives for pricing, tweet search, follower exports, replies, profiles, timelines, webhooks, SDKs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this Twitter API alternative and pricing guide to compare real X tasks: search tweets, export followers and replies, retrieve profiles and timelines, post tweets, send direct messages, monitor accounts or keywords, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current X API pricing, access rules, and product terms on the [official pricing page](https://docs.x.com/x-api/getting-started/pricing) before buying. ## Quick answer You already have approved developer access, platform terms match the project, and your team wants direct control over endpoint design. You want ready endpoints for tweet search, user lookup, followers, writes, exports, monitors, and signed webhooks without building the surrounding tooling. ## Is Xquik Better Than X API v2 for Scraping? Not for every project. Choose X API v2 when direct native platform access is required. Choose Xquik for ready tweet search, follower exports, and reply exports. It also adds pagination, files, monitors, webhooks, SDKs, and MCP. Compare the same task in both products. Measure returned fields, cursor behavior, retries, export formats, delivery options, and total operating cost. ## Source-backed X API scope X's official overview describes the X API as programmatic access to public conversation, with capabilities for reading posts, publishing content, managing users, analyzing metrics and trends, sending DMs, managing lists, and working with Spaces. The same overview presents X API v2 as the recommended version for new projects. The official OAuth 2.0 overview describes Bearer Token authentication tied to the developer App for reading public information and OAuth 2.0 Authorization Code Flow with PKCE for user-context access. Use that distinction when comparing tweet search with account actions such as posting, follows, DMs, or list management. The official Search Posts docs list `GET /2/tweets/search/recent` for last-7-day search and `GET /2/tweets/search/all` for full-archive search. Recent search is available to all developers and supports up to 100 posts per request; full-archive search is available to pay-per-use and Enterprise customers and supports up to 500 posts per request. The official Follows docs list `GET /2/users/:id/followers`, `GET /2/users/:id/following`, `POST /2/users/:id/following`, and `DELETE /2/users/:source_user_id/following/:target_user_id` for follower lookup and follow management. The official pagination docs describe the standard loop: request `max_results`, read the next-page cursor from `meta`, send it on the following request, and repeat until no cursor is returned. Use this when estimating engineering work for search, timeline, follower, and following exports. The official pricing docs describe credit-based pay-per-usage, per-resource reads, per-request writes, Owned Reads at USD 0.001 per resource for eligible app-owned data, 24-hour UTC deduplication for billable resources, Developer Console usage tracking, auto-recharge, and spending limits. ## Official X API Migration Map Use this map to move one API-backed workflow at a time. Keep the old call live, ship the matching Xquik call behind a feature flag, then compare the record shape, pagination cursor, export path, and downstream owner before switching. Official X API calls use `https://api.x.com/2` plus Bearer or OAuth user-context auth. Xquik REST calls use `https://xquik.com/api/v1` plus the `x-api-key` header. Replace `GET /2/tweets/search/recent` or `GET /2/tweets/search/all` with `GET /api/v1/x/tweets/search?q={query}`. Keep supported X search operators in `q`, then resume with `cursor`. Replace `GET /2/tweets/:id` with `GET /api/v1/x/tweets/{id}`. Replace `GET /2/tweets?ids=...` with `GET /api/v1/x/tweets?ids=...` for batches up to 100 tweet IDs. Replace `GET /2/users/:id` and `GET /2/users/by/username/:username` with `GET /api/v1/x/users/{id}`. Xquik accepts a username or numeric user ID. Replace `GET /2/users?ids=...` with `GET /api/v1/x/users/batch?ids=...`. Replace user timeline and mention timeline calls with `GET /api/v1/x/users/{id}/tweets` and `GET /api/v1/x/users/{id}/mentions`. Replace follower and following list reads with `GET /api/v1/x/users/{id}/followers` and `GET /api/v1/x/users/{id}/following`. Use `GET /api/v1/x/tweets/{id}/quotes`, `GET /api/v1/x/tweets/{id}/retweeters`, `GET /api/v1/x/tweets/{id}/favoriters`, and `GET /api/v1/x/tweets/{id}/replies` when the task is quote, repost, like, or reply analysis. Official X API pagination reads `meta.next_token` and sends it back as `pagination_token`. Xquik list and search pages return `next_cursor` and accept it as `cursor`. For CSV, JSON, XLSX, Markdown, PDF, or TXT files, create an extraction job and call `GET /api/v1/extractions/{id}/export`. ## Migration Checkpoint Store a small checkpoint per replaced workflow so another engineer can verify the endpoint pair and resume from the last page. ```json theme={null} { "migration": "official_x_api_to_xquik", "old_endpoint": "GET /2/users/:id/followers", "new_endpoint": "GET /api/v1/x/users/{id}/followers", "cursor_map": { "old_response": "meta.next_token", "old_request": "pagination_token", "new_response": "next_cursor", "new_request": "cursor" }, "handoff": { "export_path": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=csv", "owner": "crm-import-worker" } } ``` ## Comparison | Area | X API | Xquik | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Teams with approved developer access that need to build directly against native X endpoints. | Teams that need tweet search, follower exports, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Official API. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Usually covers one area: raw API access, stream delivery, publishing calls, or self-hosted collection code. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Pay-per-usage. Official docs list post reads at USD 0.005/resource, follower reads at USD 0.010/resource, and content creates at USD 0.015/request. Check the Developer Console for current rates. | Xquik starts at USD 20/month with 140,000 included credits. Tweet search and follower exports cost 1 credit/result. Top-up credits cost USD 0.00015 each. Seven direct MPP operations cost USD 0.00015 to USD 0.00075 per call. | | Integration effort | Plan for auth, pagination, retries, storage, exports, alerts, and operational tooling. | Use a dashboard tool first, then call the same task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | The official X API is the native platform API for approved developer access. | Xquik provides a ready dashboard, REST API, webhooks, exports, and MCP for common tasks: tweet search, user lookup, follower export, monitors, writes, and webhooks. | | Access model | Direct platform developer access. | API keys plus ready dashboard tools. | | Setup | Developer account, app setup, and platform approval flow. | Xquik account, API key, and ready-made tools. | | Scope | Raw API. | Tools, exports, signed webhooks, MCP, and monitors. | ## Operating model Compare X API and Xquik by output, cost, and handoff. The official X API is the native platform API for approved developer access. Xquik provides a ready dashboard, REST API, webhooks, exports, and MCP for common tasks: tweet search, user lookup, follower export, monitors, writes, and webhooks. Access model: Direct platform developer access. Xquik: API keys plus ready dashboard tools. Setup: Developer account, app setup, and platform approval flow. Xquik: Xquik account, API key, and ready-made tools. Scope: Raw API. Xquik: Tools, exports, signed webhooks, MCP, and monitors. ## Twitter API pricing & cost checkpoint X now presents the X API as pay-per-usage. Price the exact unit you need before building. The table below maps common X tasks to the public unit that drives cost. Official X API pricing also lists Owned Reads at USD 0.001 per resource when a developer app accesses its own posts, bookmarks, followers, likes, lists, and similar owned data. Treat that as a separate path from general post reads, follower reads, writes, DMs, and monitor-style workflows. | Task | X API unit to price | Xquik unit to price | | ------------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Search 1,000 posts | Posts read: USD 0.005 per resource in the official pricing docs. | Search tweets: 1 credit per tweet. Top-up credits cost USD 0.00015 each; included subscription credits can lower the effective per-credit rate. | | Export 1,000 followers | Following/follower read: USD 0.010 per resource in the official pricing docs. | Followers endpoint: 1 credit per user. Top-up credits cost USD 0.00015 each; CSV, JSON, XLSX, Markdown, and API outputs are ready in Xquik. | | Publish 100 posts | Content create: USD 0.015 per request, or USD 0.200 per request with URL in the official pricing docs. | Create tweet or reply: 30 credits per call. Upload media separately when needed, also 10 credits per call. | | Monitor accounts | Price stream or polling access, storage, retries, alerting, and webhook delivery. | Active monitors check every 1 second and cost 21 credits per monitor-hour. Stored events and webhook delivery management are included. | | Work with your own account data | Owned Reads: USD 0.001 per resource for eligible owned-data endpoints in the official pricing docs. | Use Xquik account, extraction, and export endpoints when the result must move to CSV, JSON, XLSX, webhook, SDK, MCP, or dashboard review. | Use official X API when direct platform access is the requirement. Use Xquik when the buyer needs the result packaged as an export, webhook, SDK call, MCP tool, or dashboard workflow. ## Xquik value to test For API comparisons, price the whole task: endpoint access, pagination, retries, storage, exports, alerts, webhooks, SDKs, and maintenance. Choose Xquik when those pieces must ship together. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm returned data, pagination, retry behavior, webhook payloads, export formats, and API ergonomics. Then compare total cost for the same task: access, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one API-backed task. Keep the current integration running while you compare output shape, latency, pagination, retries, exports, and alert delivery in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same record quality with less glue code or lower cost. Price the real workload. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. On X API, use the official per-resource and per-request prices in the Developer Console, then add any engineering time for storage, exports, retries, dashboards, SDKs, and webhooks. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current X API pay-per-usage rates before making a final decision. # Xanguard Alternative for Crypto Alerts | Monitor API Source: https://docs.xquik.com/alternatives/xanguard Compare Xanguard with Xquik for crypto tweet alerts, keyword monitors, tweet search, follower exports, signed webhooks, REST APIs, and MCP. See examples.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Xanguard or Xquik is better for specific X tasks: search tweets, export followers, monitor accounts or keywords, publish actions, send webhooks, and connect apps or agents. This is a factual comparison and migration guide. Verify current Xanguard pricing, tracked-account limits, delivery channels, and product terms on the [official site](https://xanguard.tech/) before buying. ## Quick answer Your team needs crypto-focused tweet alerts, Telegram delivery, WebSocket events, follow alerts, profile change alerts, or community monitoring for watched accounts. You need tweet search, follower exports, media uploads, DMs, write actions, 1-second monitors, signed webhooks, SDKs, or MCP from one product. ## Source-backed Xanguard scope Xanguard's official home page positions the product as sub-second Twitter/X alerts for crypto. It lists delivery through Telegram, REST API, HMAC-signed webhooks, and WebSocket streaming on B2B plans. The home page lists 14 REST API endpoints, 4 delivery channels, HMAC-SHA256 webhook signatures, 8 products, a free tier, and plans from USD 19/month paid in SOL. Xanguard's public workflow describes adding accounts, detecting new tweets, applying keyword filters, mute rules, reply and repost exclusions, contract detection, and delivering alerts through Telegram, webhooks, REST clients, or B2B WebSocket. The official B2B page describes 4 real-time modules in one connection: tweets, follows, profile changes, and community monitoring. It says tweet events include replies, quotes, retweets, full untruncated text, media URLs, quoted tweet content, and contract address extraction. B2B setup uses Telegram bot or REST API target creation, then a WebSocket connection at `wss://api.xanguard.tech/v1/dt/realtime/ws` with API-key login through an opcode protocol. B2B pricing lists RT 25 at USD 49/month for 25 tracked accounts, RT 100 at USD 149/month for 100 tracked accounts, RT 500 at USD 499/month for 500 tracked accounts, and Enterprise custom pricing above 500 accounts. The B2B plan cards list WebSocket, REST API, webhook delivery, Telegram delivery, and all 4 real-time modules on RT 25, RT 100, and RT 500. RT 100 adds priority support. The page also says plans are payable with SOL and Telegram Stars, and that activation happens after payment confirmation. ## Comparison | Area | Xanguard | Xquik | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Use when | Crypto teams that need sub-second tweet alerts, contract detection, community activity, or WebSocket events for trading bots. | Teams that need tweet search, follower exports, media uploads, DMs, write actions, 1-second monitors, signed webhooks, SDKs, and MCP together. | | Product type | Crypto monitoring API. | Platform for tweet search, follower exports, account actions, monitors, webhooks & files. | | Coverage | Tweet alerts, community watch, convergence tracking, trending alerts, REST API, Telegram, WebSocket on B2B plans, and HMAC-SHA256 signed webhooks. B2B includes tweets, follows, profile changes, and community activity in one WebSocket. | 47 dashboard tools, 128 REST operations, 23 extraction tools, 17 X write actions, account and keyword monitors, webhooks, giveaway draws, Radar, 10 SDKs, MCP, and pay-per-use reads. | | Pricing & value | Xanguard says consumer plans start at USD 19/month paid in SOL. B2B plans list RT 25 at USD 49/month, RT 100 at USD 149/month, RT 500 at USD 499/month, and custom pricing above 500 accounts. | Xquik starts at USD 20/month with 140,000 included credits. Tweet search and follower exports cost 1 credit/result. Common X write calls cost 10 credits/call. Top-ups cost USD 0.00015/credit, and webhook plus stored-event management is free. | | Integration effort | Use Telegram setup, Bearer-auth REST calls, HMAC webhooks, or a persistent WebSocket with a login opcode on B2B plans. | Use a dashboard tool first, then call the same X task through REST, webhook, SDK, export, or MCP when it needs automation. | | Summary | Xanguard focuses on crypto alerting for watched Twitter and X accounts. | Xquik fits teams that need broader tweet, profile, follower, reply & account-action coverage: REST endpoints, extractions, follower data, monitors, webhooks, SDKs, MCP, and dashboard tools. | | Primary use case | Crypto-focused X alerts and monitoring. | Tweet, profile, follower, reply, post, monitor & export workflows. | | Delivery model | Real-time alerting and monitoring channels. | Dashboard tools, REST API, exports, and signed webhooks. | | Signal focus | Trading teams watching fast crypto signals. | Builders and operators automating many X tasks. | ## Operating model Compare Xanguard and Xquik by output, cost, and handoff. Xanguard focuses on fast Twitter and X monitoring for crypto alerts, watchlists, and trading systems. Xquik fits teams that need broader tweet, profile, follower, reply & account-action coverage: REST endpoints, extractions, follower data, monitors, webhooks, SDKs, MCP, and dashboard tools. Primary use case: Crypto-focused X alerts and monitoring. Xquik: Tweet, profile, follower, reply, post, monitor & export workflows. Delivery model: Real-time alerting and monitoring channels. Xquik: Dashboard tools, REST API, exports, and signed webhooks. Signal focus: Trading teams watching fast crypto signals. Xquik: Builders and operators automating many X tasks. ## Current cost checkpoint Use Xanguard when the job is crypto alert delivery for tracked accounts. Use Xquik when the same project also needs searchable tweet and profile records, follower exports, monitor events & webhooks, exports, write actions, SDKs, MCP, or dashboard review. | Task | Xanguard unit to price | Xquik unit to price | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | Track 25 accounts for trading alerts | B2B RT 25 lists 25 tracked accounts at USD 49/month, with tweets, follows, profile changes, community activity, WebSocket, REST API, webhooks, and Telegram. | 25 active monitors use 525 credits/hour. At top-up pricing, 24/7 monitoring for 30 days is about USD 56.70 before included monthly credits. | | Track 100 accounts | B2B RT 100 lists 100 tracked accounts at USD 149/month with all 4 B2B modules and priority support. | 100 active monitors use 2,100 credits/hour. Use Xquik when the same account list also needs stored events, REST reads, exports, webhooks, SDKs, or MCP. | | Search old tweets or export followers | Xanguard is focused on live alerts and watched-account signals. | Search tweets and follower exports cost 1 credit/result, with CSV, JSON, XLSX, Markdown, API, SDK, and MCP handoff options. | | Publish, upload media, or send DMs | Not the core Xanguard task. | Common X write calls cost 10 credits/call. Upload media also costs 10 credits/call. | Choose Xanguard for crypto alert speed and watched-account WebSocket feeds. Choose Xquik when the same X account or keyword work must turn into data exports, dashboards, signed webhooks, API calls, SDK jobs, MCP tools, or connected-account actions. ## Xquik value to test For API comparisons, price the whole task: endpoint access, pagination, retries, storage, exports, alerts, webhooks, SDKs, and maintenance. Choose Xquik when those pieces must ship together. 23 extraction tools cover tweets, replies, quotes, reposts, likes, followers, following, verified followers, communities, lists, Spaces, articles, and search. Export results to CSV, JSON, XLSX, Markdown, or API responses. 17 X write actions cover tweets, media uploads, likes, retweets, follows, DMs, profile updates, and community actions from connected accounts. Account and keyword monitors check active streams every 1 second. Events can be stored, polled, or delivered through signed webhooks. 128 REST operations, 10 SDKs, MCP, pay-per-use read endpoints, API keys, and transparent credit billing keep integration work small. ## What to verify in a trial Use a real task: search a keyword, export followers, upload media, send a DM, monitor an account, or deliver a webhook. Compare outputs, not feature labels. Confirm returned data, pagination, retry behavior, webhook payloads, export formats, and API ergonomics. Then compare total cost for the same task: access, included volume, top-ups, and engineering time. Decide where the result goes next: CSV, JSON, webhook, SDK call, MCP tool, dashboard review, or another API. ## Migration path Start with one API-backed task. Keep the current integration running while you compare output shape, latency, pagination, retries, exports, and alert delivery in Xquik. Keep the test small: one task, one output, one cost model, and one downstream owner. Switch only when Xquik gives the same X record quality with less glue code or lower cost. Compare Xanguard and Xquik by alert latency, tracked-account limits, delivery channels, REST breadth, export needs, write actions, and agent handoff. Price the real workload. On Xanguard, price watched accounts, required modules, delivery channel, and SOL payment flow. On Xquik, Starter is USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook and stored-event management are free, and active monitors bill only while enabled. Review REST API authentication, endpoint groups, and response patterns. Map dashboard tools to REST calls, signed webhooks, exports, and MCP tools. Check included credits, top-ups, free operations, and active monitor billing. Verify current consumer alert products, REST API endpoints, delivery channels, and payment options. Verify current tracked-account limits, modules, and delivery channels before making a final decision. # Zapier Alternative for X Automation | API Comparison Source: https://docs.xquik.com/alternatives/zapier Compare Zapier with Xquik for X/Twitter automation, tweet search, follower exports, webhooks, APIs, MCP, and workflow handoffs. Compare costs and API coverage.
For the complete documentation index, see llms.txt.
Use this guide to decide whether Zapier, Xquik, or both fit a workflow that needs X/Twitter data, account actions, alerts, exports, webhooks, API calls, or downstream app automation. This is a factual comparison and migration guide. Verify current Zapier plans, task tiers, Webhooks by Zapier behavior, API by Zapier behavior, Platform CLI requirements, REST Hooks, and platform terms on official Zapier pages before buying. ## Quick answer You need a no-code automation builder that connects many apps, routes records between teams, and lets operators maintain Zaps without writing code. You need focused X API tasks: tweet search, follower exports, media uploads, DMs, 1-second monitors, signed webhooks, SDKs, MCP, and credit-priced API calls. Zapier should orchestrate app-to-app steps while Xquik supplies tweet search results, follower exports, account actions & monitor events, write actions, monitor events, webhook payloads, exports, or API responses. ## Source-backed Zapier scope Zapier's official API-request guidance says API by Zapier and Webhooks by Zapier can call any API endpoint even when the service has no dedicated Zapier app. That guidance lists Webhooks by Zapier for Zaps, Agents, and Zapier MCP with Basic Authentication or no authentication, Catch Hook, Catch Raw Hook, Retrieve Poll, Custom Request, GET, PUT, and POST. It lists API by Zapier for Zaps, Agents, and Zapier MCP with OAuth2, static headers, or no authentication, New Item from API, API Request, exact request passthrough, JQ extraction, and clear error messages. Zapier's official REST Hook CLI docs say `subscribeHook` receives `bundle.targetUrl`, returns an id for unsubscribe, runs through `performSubscribe`, and `unsubscribeHook` runs through `performUnsubscribe` when the Zap turns off. Zapier's official webhook rate-limit docs list `429` behavior at `20,000 requests every 5 minutes` per user and `1,000 requests every 5 minutes` per Zap for legacy webhook routes, including subscriptions and REST webhooks. Zapier's official pricing page currently describes a unified plan for Zaps, Tables, Forms, and Zapier MCP, task tiers from 100 tasks/month through custom task limits, and MCP tool calls that use two tasks from the plan quota. ## Comparison | Area | Zapier | Xquik | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Use when | Teams need no-code Zaps, app connections, task routing, forms, tables, AI orchestration, and operational handoffs across many systems. | Teams need X/Twitter records, account actions, monitor events, exports, signed webhooks, SDKs, and MCP from one X-focused platform. | | Product type | Automation platform with Zaps, app connections, Webhooks by Zapier, API by Zapier, Zapier MCP, Tables, Forms, and Platform CLI integrations. | Tweet search results, follower exports, account actions & monitor events, account actions, monitors, webhooks, exports, dashboard tools, REST API, SDKs & MCP. | | X/Twitter path | Use Zapier's existing app options, API by Zapier, Webhooks by Zapier, or a private Zapier Platform CLI integration that calls an X service. | Start with a dashboard tool, REST endpoint, SDK call, export, webhook subscription, or MCP tool for a defined X task. | | Returned data | Zap step outputs, app fields, webhook payloads, tables, forms, task history, and downstream app records shaped inside Zapier. | API responses, CSV/JSON/XLSX exports, monitor events, webhook payloads, action logs, and MCP responses. | | Cost model | Zapier pricing is based on task tiers, plan features, billing interval, app usage, and add-ons. Check the official pricing page before committing. | Starter is USD 20/month with 140,000 included credits. Top-ups are USD 0.00015/credit, webhook management is free, and active monitors bill only while enabled. | | API fit | Zapier is the orchestration layer. API by Zapier can store API-key credentials in a connection, while Webhooks by Zapier fits simple webhook-style payloads. | Xquik is the X API layer. It handles X-specific endpoints, pagination, exports, account actions, monitors, signed webhooks, SDKs, and MCP. | | Summary | Zapier is useful when the main problem is connecting many apps with no-code maintenance. | Xquik is useful when the main problem is reliable tweet search results, follower exports, account actions & monitor events, X account actions, monitoring, webhooks, exports, and agent handoff. | ## Best combined Zap For X/Twitter automation, Zapier and Xquik usually fit together. Let Zapier own app routing, operator-owned Zaps, CRM updates, ticket creation, Slack alerts, Sheets rows, and team handoffs. Let Xquik own tweet search, user lookups, follower exports, media uploads, DMs, monitor events, signed webhooks, API response contracts, and MCP. Zapier: create the Zap trigger, destination app steps, field mapping, and error handling. Xquik: create one API key for the X task. Zapier: route step outputs into apps. Xquik: return tweet records, user records, exports, monitor events, webhook payloads, or API responses. Zapier: send results to CRMs, Slack, Sheets, queues, tickets, or email. Xquik: supply REST, signed webhooks, SDKs, exports, and MCP. ## Xquik workflows to run from Zapier Use Xquik inside Zapier when the Zap needs a concrete X-specific API step before routing data to other apps. Call `GET /x/tweets/search`, filter by query, author, language, date, or engagement, then create or update CRM records from the returned tweet fields. Create an extraction job, poll until `completed`, then export CSV, XLSX, or JSON and map stable X user IDs into Sheets, Airtable, or a warehouse. Register a Zapier REST Hook or Catch Hook URL in Xquik, then route signed account or keyword monitor payloads by event type and author. Use Zapier for approvals and source records, then call Xquik to create tweets, upload media, send DMs, or run other account actions. ## Monitor webhook receiver handoff When a Zapier REST Hook, Catch Hook, or Catch Raw Hook receives Xquik monitor events, verify `X-Xquik-Signature` before field mapping. Store `deliveryId` and `streamEventId` as separate Zap storage keys: use `deliveryId` for endpoint retry de-dupe and `streamEventId` when one monitor event should process once across webhook changes. Return `2xx` after accepting a duplicate `deliveryId` or `streamEventId`; downstream Zap steps can skip the already-processed row. Keep shared Zap rows to `deliveryId`, `streamEventId`, `eventType`, `occurredAt`, `username` or `query`, and mapped tweet fields. Do not store endpoint signing values, raw request body, raw signature, or full headers in Zap history, tables, Slack messages, CRM rows, or retry queues. ## API request path Choose the Zapier request surface by the credential and reuse model. | Need | Zapier path | Xquik detail | | -------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | One private Zap that calls Xquik | API by Zapier | Store the Xquik API key in a Zapier app connection and send `x-api-key` on each request. | | A simple incoming trigger | Webhooks by Zapier | Use Catch Hook or Catch Raw Hook when Xquik should send monitor events to a Zap URL. | | A reusable private app | Zapier Platform CLI | Build triggers, creates, searches, auth, sample output, and REST Hooks around Xquik endpoints. | | Monitor event webhooks | REST Hooks | Use `bundle.targetUrl` as the callback URL when subscribing, store the returned Xquik webhook ID, and delete it on unsubscribe. | Zapier's API-request guidance says Webhooks by Zapier is best for simple or no-auth requests, while API by Zapier is better when an API key should live in a connection. Use API by Zapier or a private Platform CLI integration for authenticated Xquik calls. Zapier's webhook rate-limit page currently lists `20,000 requests every 5 minutes` per user and `1,000 requests every 5 minutes` per Zap for legacy webhook routes. Check that page before routing high-volume monitor events into Zapier. ## Trial checklist Use one task: tweet search, follower export, monitor alert, media upload, DM send, or account action. Add the trigger, Xquik API by Zapier or Webhooks step, field mapping, destination app, and failure path. Confirm returned fields, pagination, `Retry-After` handling, webhook signature verification, export format, and downstream mapping. Compare Zapier task tiers, premium app needs, Zapier webhook limits, Xquik credits, active monitor billing, and engineering time for retries and alerts. ## Migration path Do not replace a working Zapier workflow first. Replace only the brittle X/Twitter step. 1. Keep the Zap trigger, destination apps, approvals, and field mappings. 2. Replace a manual export, unofficial scraper, or custom X request with a Xquik REST call, extraction, webhook, or private Platform CLI action. 3. Map Xquik fields into the existing Zap output shape. 4. Add explicit paths for `401`, `402`, `429`, and `5xx` responses. 5. For webhooks, retry non-2xx responses with exponential backoff and keep Zapier's current webhook rate limits in mind. ## Official sources to verify Verify current task tiers, plan features, billing interval, add-ons, Tables, Forms, and Zapier MCP inclusion. Verify when to use API by Zapier, Webhooks by Zapier, API Request actions, and Custom Actions. Verify `bundle.targetUrl`, subscribe, unsubscribe, sample output, and REST Hook trigger behavior. Verify current Webhooks by Zapier 429 behavior, per-user limits, per-Zap limits, and delayed processing notes. ## Xquik next steps Build a private Zapier integration with API-key auth, REST Hooks, actions, polling triggers, and tests. Export followers, map CRM fields, and hand off CSV, XLSX, or JSON. Deliver signed monitor events to Zapier, queues, CRMs, Slack, databases, or backend services. Check included credits, top-ups, free operations, and active monitor billing. # Xquik Account Details & Subscription Status Source: https://docs.xquik.com/api-reference/account/get GET /account Retrieve subscription state, API credit balance, monitor usage and billing, automatic top-up settings, and the connected X username for an Xquik account. ```json theme={null} { "plan": "active", "monitorsUsed": 3, "monitorsAllowed": 9007199254740991, "monitorBilling": { "activeDailyEstimate": "1500", "activeHourlyBurn": "63", "creditsPerActiveMonitorDay": "500", "creditsPerActiveMonitorHour": "21", "eventsIncluded": true }, "creditInfo": { "balance": "50000", "lifetimePurchased": "140000", "lifetimeUsed": "90000", "autoTopupEnabled": false, "autoTopupAmountDollars": 10 }, "xUsername": "elonmusk" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl https://xquik.com/api/v1/account \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/account", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const account = await response.json(); process.stdout.write(`${JSON.stringify(account, null, 2)}\n`); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/account", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) account = response.json() print(account) ``` ```go Go theme={null} package main import ( "fmt" "io" "log" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/account", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body)) } ``` ## Run an API account preflight Call `GET /account` before a long tweet search, follower export, scheduled write, or monitor rollout. The response separates subscription access, available credits, active monitor cost, automatic top-up settings, and the linked X username. | Preflight question | Response field | Integration decision | | ------------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------- | | Is the subscription active? | `plan` | Report subscription state. Check `creditInfo.balance` before metered work. | | How many credits remain? | `creditInfo.balance` | Compare the Bigint string with the estimated tweet, follower, or extraction cost. | | How many monitors are active? | `monitorsUsed` | Reconcile account and keyword monitors before enabling another one. | | What is the current hourly monitor charge? | `monitorBilling.activeHourlyBurn` | Include active monitors in the hourly credit budget. | | What is the estimated daily monitor charge? | `monitorBilling.activeDailyEstimate` | Add the rounded estimate to the daily operating forecast. | | Is automatic top-up enabled? | `creditInfo.autoTopupEnabled` | Confirm the account may replenish credits without a manual checkout. | | Which X identity is linked? | `xUsername` | Compare the username with the intended tweet, reply, or follower workflow. | Use `plan` to report subscription state. Do not use it as a credit gate. Funded pay-as-you-go requests can continue while `plan` is `inactive`. Treat missing `creditInfo` as no recorded balance, not numeric zero. Do not use `monitorsAllowed` as a capacity limit. The field is deprecated and always reports the maximum safe integer. Use `monitorsUsed` and the monitor billing fields for cost checks. ## Store an account billing snapshot Persist a timestamped account snapshot before a worker starts billable work. Never convert credit strings to an unsafe JavaScript number. | Stored value | Source | Use | | ---------------------- | ------------------------------------ | ------------------------------------------------------------------------------ | | Subscription state | `plan` | Explain why a job started or stopped. | | Credit balance | `creditInfo.balance` | Reconcile credits before and after tweet, follower, write, or extraction work. | | Purchased credits | `creditInfo.lifetimePurchased` | Compare account funding with cumulative usage. | | Used credits | `creditInfo.lifetimeUsed` | Track the account-level consumption total. | | Active monitors | `monitorsUsed` | Join the snapshot to account and keyword monitor inventories. | | Hourly monitor burn | `monitorBilling.activeHourlyBurn` | Detect an unexpected monitor-cost increase. | | Daily monitor estimate | `monitorBilling.activeDailyEstimate` | Forecast the next 24 hours of active monitor usage. | | Automatic top-up state | `creditInfo.autoTopupEnabled` | Route low-balance alerts to manual or automatic recovery. | | Linked X username | `xUsername` | Confirm the connected identity used by account write workflows. | ## Headers Your API key. Generate one from the [API Keys page](https://xquik.com). ## Response ### 200 OK Subscription status. `"active"` or `"inactive"`. Deprecated. Monitor slots are unlimited, so this is always `9007199254740991`. Number of currently active account monitors and keyword monitors. Active monitor billing details. **Monitor billing object fields:** Estimated daily credits for currently active monitors. Credits charged each hour for currently active monitors. Rounded daily credit estimate for 1 active monitor. Hourly credits charged for 1 active monitor. Whether webhook and event deliveries are included in monitor billing. Active monitor check interval in seconds. Whether monitor slot count is unlimited. Credit balance details. Omitted if no credit balance row exists yet. **Credit info object fields:** Current credit balance (Bigint string to preserve precision above Number.MAX\_SAFE\_INTEGER). Total credits purchased across all time (Bigint string). Total credits consumed across all time (Bigint string). Whether automatic credit top-up is enabled. Dollar amount charged when automatic top-up runs. Credit balance threshold that triggers automatic top-up when enabled (Bigint string). Linked X username. Omitted when no X account is connected. ```json theme={null} { "plan": "active", "monitorsAllowed": 9007199254740991, "monitorsUsed": 3, "monitorBilling": { "activeDailyEstimate": "1500", "activeHourlyBurn": "63", "creditsPerActiveMonitorDay": "500", "creditsPerActiveMonitorHour": "21", "eventsIncluded": true, "instantCheckIntervalSeconds": 1, "unlimitedSlots": true }, "creditInfo": { "balance": "50000", "lifetimePurchased": "140000", "lifetimeUsed": "90000", "autoTopupEnabled": false, "autoTopupAmountDollars": 10, "autoTopupThreshold": "50000" }, "xUsername": "elonmusk" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. Check the `x-api-key` header value. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Credit balance Every subscriber gets a monthly credit allowance. Metered reads bill per result or call. Writes bill per action. Active monitors bill 21 credits per hour. `monitorsUsed`, `monitorBilling.activeHourlyBurn`, and `monitorBilling.activeDailyEstimate` include active account monitors and active keyword monitors. The daily estimate is rounded. At 0 credits, metered calls return `402`. **Related:** [Authentication](/api-reference/authentication) · [Billing & Usage](/guides/billing) # X API Subscription Checkout & Billing Portal Source: https://docs.xquik.com/api-reference/account/subscription-checkout POST /subscribe Create a confirmed Xquik API subscription checkout or billing portal URL. Route new, active & payment-issue accounts without automatically charging the account. ```json theme={null} { "url": "https://xquik.com/billing/session", "status": "checkout_created", "message": "Billing session created" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Returns a checkout URL for new subscribers or a billing portal URL for existing subscribers. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/subscribe \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/subscribe", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); // Redirect user to data.url ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/subscribe", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() # Redirect user to data["url"] ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://xquik.com/api/v1/subscribe", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Create an X API Subscription Checkout Call `POST /subscribe` after an authenticated user confirms a billing action. The request creates a hosted URL. It never completes payment automatically. Use this route for 3 account states: | Account state | Response status | Returned destination | | ----------------------------------- | -------------------- | --------------------------------------------- | | No active subscription | `checkout_created` | Hosted subscription checkout | | Active or trialing subscription | `already_subscribed` | Billing portal for plan or payment management | | Past-due or incomplete subscription | `payment_issue` | Billing portal for payment recovery | Always route by `status`. Do not infer the destination from the URL hostname. Use only the URL returned by the current response. This endpoint does not quote current Twitter API pricing or X API pricing. Review the [live Xquik pricing page](https://xquik.com/#pricing) before checkout. The pricing page remains authoritative for tiers, credits, and current terms. ## Choose a Subscription Tier Send `tier` only when the user selected a specific Xquik plan. | Tier value | Checkout behavior | | ---------- | ------------------------------------------- | | `starter` | Pre-select Starter. | | `pro` | Pre-select Pro. | | `business` | Pre-select Business. | | Omitted | Let the user choose on the hosted checkout. | The field pre-selects a tier. It does not activate that tier by itself. Send only the documented enum values. ## Complete a Safe Billing Handoff 1. Show the current plan and credit terms before confirmation. 2. Call this endpoint once for the confirmed action. 3. Check the HTTP status before reading `url`. 4. Send the user to the returned hosted URL. 5. Keep the billing URL out of logs and analytics. 6. Recheck [`GET /account`](/api-reference/account/get) after the user returns. Read `plan` to confirm subscription state. Read `creditInfo.balance` before tweet search, follower exports, writes, or monitor work. Subscriptions are not the only funding path. A funded pay-as-you-go account can continue eligible work while `plan` is `inactive`. ## Handle Checkout Retries The service can reuse a matching open checkout. Treat the latest returned URL as authoritative. A different tier request can replace an older open checkout. Do not repeatedly call this endpoint from a timer or automatic retry loop. For `429`, wait for `Retry-After`. For `401`, replace the credential first. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Body Pre-select `starter`, `pro`, or `business`. Omit the body when the user should choose on the hosted checkout. ## Response ### 200 OK Checkout URL (new subscribers) or billing portal URL (existing subscribers). Redirect the user to this URL. Human-readable message describing the action taken. One of: `already_subscribed`, `checkout_created`, `payment_issue`. ```json theme={null} { "url": "https://xquik.com/billing/checkout/session", "message": "Complete checkout at the URL below to start your subscription.", "status": "checkout_created" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [Get Account](/api-reference/account/get) to check current subscription status and usage. # Update Xquik Account Language & Locale API Source: https://docs.xquik.com/api-reference/account/update PATCH /account Set the Xquik dashboard locale to English, Turkish, or Spanish. Send a locale code, receive a success confirmation, and handle validation or rate-limit errors. ```json theme={null} { "success": true } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X PATCH https://xquik.com/api/v1/account \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "locale": "tr" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/account", { method: "PATCH", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ locale: "tr" }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests response = requests.patch( "https://xquik.com/api/v1/account", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"locale": "tr"}, ) data = response.json() print(data) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]string{ "locale": "tr", }) req, err := http.NewRequest("PATCH", "https://xquik.com/api/v1/account", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Change the Xquik Dashboard Language Send one locale code to change the language used by supported Xquik screens. The account locale controls dashboard labels, notices, and supported messages. It does not translate tweets, replies, profiles, or media from X. | Dashboard language | `locale` value | Example body | | ------------------ | -------------- | -------------------- | | English | `en` | `{ "locale": "en" }` | | Turkish | `tr` | `{ "locale": "tr" }` | | Spanish | `es` | `{ "locale": "es" }` | Send exactly one supported value. Locale codes are lowercase and case-sensitive. Values such as `EN`, `en-US`, or `de` fail validation. This endpoint changes only the Xquik account locale. It leaves connected X accounts and profile settings unchanged. It does not alter tweet language or X API content. ## Confirm an Account Locale Update A successful request returns `{ "success": true }`. This response confirms the accepted update. It does not echo the locale or return an account object. Keep the locale from your request when the client needs an audit record. The [Get Account](/api-reference/account/get) response reports subscription, credits, monitor billing, and the connected X username. It does not return the saved locale. | Client check | Expected result | Next action | | -------------------- | ----------------------------------------- | ------------------------------------------- | | HTTP status is `200` | Locale update was accepted | Store the requested locale when needed. | | `success` is `true` | The response matches the success contract | Continue without parsing an account object. | | Response is `400` | Locale is missing or unsupported | Send `en`, `tr`, or `es`. | | Response is `401` | Authentication failed | Replace the missing or invalid credential. | | Response is `429` | The request exceeded the rate limit | Wait for `Retry-After`, then retry once. | ## Handle Locale Validation Safely Validate the code before sending the request. Do not retry a `400` response with the same body. Correct the locale first. For `401`, provide a valid API key or bearer token. Browser sessions may use the supported session cookie. Never place credentials in query parameters. For `429`, pause the locale update. Read the `Retry-After` header and retry after that interval. Do not run parallel retries for one account setting. ### How Do I Change Xquik to Turkish or Spanish? Send `{ "locale": "tr" }` for Turkish. Send `{ "locale": "es" }` for Spanish. Use `{ "locale": "en" }` to return to English. ### Does This Change the Language of Tweets or Replies? No. Xquik stores the account interface preference. Tweet text, reply text, profile bios, timelines, and media remain unchanged. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body Locale preference for the account. Must be one of `en`, `tr`, or `es`. ## Response ### 200 OK Always `true` on successful update. ```json theme={null} { "success": true } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input" } ``` The `locale` field is missing or unsupported. Send `en`, `tr`, or `es`. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [Get Account](/api-reference/account/get) for subscription, credits, monitor billing, and the connected X username · [Authentication](/api-reference/authentication) # Set a Twitter Handle for Xquik Tweet Style Analysis Source: https://docs.xquik.com/api-reference/account/x-identity PUT /account/x-identity Store your lowercase X or Twitter handle for own-account tweet style analysis. Validate username format, replace a handle, and recover from 400, 401 or 429. ```json theme={null} { "success": true, "xUsername": "elonmusk" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Store Your X or Twitter Handle Store one X username on your authenticated Xquik account. Send the handle without its leading `@`. Xquik validates the syntax and stores lowercase text. This identity helps [Analyze Style](/api-reference/styles/analyze) recognize when the requested username matches your stored account username. That match supports own-account tweet style analysis. This route does not look up an X profile. It does not verify username availability, account existence, or ownership. It also does not connect an X account or authorize tweet, reply, like, follow, or Direct Message actions. **Free** - does not consume credits ```bash cURL theme={null} curl --fail-with-body \ --request PUT https://xquik.com/api/v1/account/x-identity \ --header "x-api-key: xq_YOUR_KEY_HERE" \ --header "Content-Type: application/json" \ --data '{"username":"elonmusk"}' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/account/x-identity", { method: "PUT", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ username: "elonmusk" }), }); const result = await response.json(); if (!response.ok) { throw new Error(`${response.status} ${result.error}: ${result.message}`); } console.log(result.xUsername); ``` ```python Python theme={null} import requests response = requests.put( "https://xquik.com/api/v1/account/x-identity", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"username": "elonmusk"}, ) result = response.json() if response.status_code != 200: raise RuntimeError( f'{response.status_code} {result["error"]}: {result["message"]}' ) print(result["xUsername"]) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, err := json.Marshal(map[string]string{ "username": "elonmusk", }) if err != nil { panic(err) } req, err := http.NewRequest("PUT", "https://xquik.com/api/v1/account/x-identity", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } if resp.StatusCode != http.StatusOK { panic(fmt.Sprintf("%d %v: %v", resp.StatusCode, result["error"], result["message"])) } fmt.Println(result["xUsername"]) } ``` ## Format the X Username Correctly Send one `username` string. Remove the `@` prefix first. The Xquik route accepts 1 to 15 characters. Every character must be a letter, number, or underscore. Spaces, periods, hyphens, emoji, and `@` are rejected. | Input | Result | Reason | | ------------- | ----------------------- | ------------------------------------- | | `XDevelopers` | Stored as `xdevelopers` | Uppercase letters are normalized. | | `xquik_api` | Stored as `xquik_api` | Letters and underscores are accepted. | | `@xquik` | `400 invalid_username` | Remove the leading `@`. | | `xquik-api` | `400 invalid_username` | Hyphens are not accepted. | | `xquik api` | `400 invalid_username` | Spaces are not accepted. | | Empty string | `400 invalid_input` | A username is required. | Xquik does not trim whitespace. Remove surrounding spaces before sending the body. Store the lowercase response value as your canonical Xquik copy. [X's username guidance](https://help.x.com/en/managing-your-account/change-x-handle) describes a handle as the unique name shown after `@`. Native X rules and availability checks can be stricter than this route's syntax validation. Passing the Xquik pattern does not prove that X currently allows the handle. It also does not prove that the handle belongs to you. ## Understand Handle, Display Name, and User ID An X username is also called a Twitter handle or X handle. It appears after `@` and inside the profile URL. This endpoint stores that username only. | Identifier | Example | Stored here? | Can it change? | | ------------------- | ----------- | ---------------- | ------------------------------------------ | | X or Twitter handle | `@xquik` | Yes, without `@` | Yes, the X account can rename it. | | Display name | `Xquik` | No | Yes | | Numeric X user ID | `123456789` | No | Normally treated as the stable profile ID. | Do not send a numeric Twitter user ID unless it is actually the account's username text. This route does not convert a Twitter ID to a username. Use [Twitter Profile Lookup](/api-reference/x/twitter-profile-lookup) when you need profile fields from a known username or user ID. Use [Search Users](/api-reference/x/search-users) when you need username search. ## Apply the Identity to Tweet Style Analysis Set the handle before analyzing your own posting style. The style route compares its requested username with this stored lowercase value. Use this workflow: 1. Find the exact current X username. 2. Remove the leading `@` symbol. 3. Send the username through this route. 4. Save the returned lowercase `xUsername`. 5. Call Analyze Style for that same username. 6. Review the style result for the intended account. 7. Update this identity after an X username change. Case does not affect the stored match. `XDevelopers` becomes `xdevelopers`. Sending the same normalized username again keeps the same stored value. Sending another valid username replaces the previous one. The response returns the new stored value. No username history appears in this response. This route provides no delete or clear operation. Replace the value with another valid username when your account identity changes. ## Keep Identity Storage Separate From X Connection This endpoint changes one Xquik account field. It does not create an authenticated connection to X. | Action | Performed here? | Correct workflow | | -------------------------------------- | --------------- | ---------------------------------- | | Store a username for style matching | Yes | Call this route. | | Verify that an X profile exists | No | Call Twitter Profile Lookup. | | Search for a Twitter username | No | Call Search Users. | | Connect an X account for write actions | No | Use the X account connection flow. | | Rename the account on X | No | Change the username through X. | | Convert a Twitter user ID to username | No | Use a profile lookup endpoint. | | Read followers, replies, or tweets | No | Use the matching X read endpoint. | Use [List Connected X Accounts](/api-reference/x-accounts/list) to inspect login-backed X connections. A stored style identity and a connected X account serve different purposes. ## Recover From X Identity Errors | Status | Error | Cause | Fix | | ------ | --------------------- | --------------------------------------------------------------------- | -------------------------------------------- | | `200` | Success object | Xquik stored the lowercase username | Save `xUsername`. | | `400` | `invalid_input` | The body is non-object or `username` is missing, empty, or non-string | Send a non-empty string. | | `400` | `invalid_username` | The string breaks the 1-to-15-character pattern | Remove `@`, spaces, and unsupported symbols. | | `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. | | `429` | `rate_limit_exceeded` | Too many requests reached the route | Wait for `Retry-After`, then retry once. | Never retry either `400` response without changing the body. Never retry `401` without replacing the credential. After `429`, keep the exact intended handle. Retry after the server's delay, then confirm the stored value through [Get Account](/api-reference/account/get). ## Answer X Username Questions ### What Is a Twitter Handle? A Twitter handle is the unique username displayed after `@`. X now calls it an X username or handle. It differs from the display name. ### How Can I Find My Twitter Username? Open your X profile or account settings. Copy the handle shown after `@`, then send it here without that symbol. ### Does This Endpoint Perform a Twitter Username Lookup? No. It stores supplied text after syntax validation. It does not fetch profile details or confirm that the username exists. ### Does It Verify That I Own the X Account? No. The route does not perform an ownership challenge. Use the separate X account connection workflow for authenticated actions. ### Is a Twitter User ID the Same as a Username? No. A username is the changeable handle. A user ID identifies the profile with a separate numeric value. ### What Happens If My X Handle Changes? Call this route again with the new handle. The valid lowercase username replaces the previous value. ### Does Setting an X Identity Consume Credits? No. This authenticated account update is free. ## Headers Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). An OAuth bearer token formatted as `Bearer YOUR_TOKEN`. Send this header or `x-api-key`, not both. Must be `application/json`. ## Body X username without `@`. Send 1 to 15 letters, numbers, or underscores. The stored value becomes lowercase. ## Response ### 200 OK Always `true` after a successful update. Stored lowercase X username without `@`. ```json theme={null} { "success": true, "xUsername": "elonmusk" } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` The body lacks a non-empty string `username`. ```json theme={null} { "error": "invalid_username", "message": "Invalid username format." } ``` The username breaks the 1-to-15-character letter, number, and underscore pattern. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Authentication is missing or invalid. Replace the credential before retrying. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests reached the route. Wait for `Retry-After` before retrying. **Related:** [Get Account](/api-reference/account/get) to confirm the stored handle, [Analyze Style](/api-reference/styles/analyze) to process tweet style, or [List Connected X Accounts](/api-reference/x-accounts/list) to inspect authenticated X connections. # Xquik API Key Creation & Request Authentication Source: https://docs.xquik.com/api-reference/api-keys/create POST /api-keys Create an API key for tweet, follower, profile, monitor, webhook, export, and X account requests. Copy the secret once and store it safely. See costs. ```json theme={null} { "id": "42", "fullKey": "xq_live_abc123def456", "name": "My API Key", "prefix": "xq_live_abc1", "createdAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "api_key_limit_reached", "message": "API key limit reached. Delete an existing key first." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Choose Credential Creation Use this route only when creating a new credential. `fullKey` appears at creation, so store it immediately in a secret manager. List operations return inventory data and never replace this storage step. **Free** - does not consume credits Store `fullKey` immediately and log only `id` and `prefix`. ```bash cURL theme={null} response=$(curl -sS -X POST https://xquik.com/api/v1/api-keys \ -H "Cookie: session_token=YOUR_SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Production"}') full_key=$(jq -r '.fullKey' <<<"$response") key_id=$(jq -r '.id' <<<"$response") key_prefix=$(jq -r '.prefix' <<<"$response") # Store $full_key in your secret manager; do not print it in logs. printf 'Created API key %s (%s)\n' "$key_id" "$key_prefix" ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/api-keys", { method: "POST", headers: { "Cookie": "session_token=YOUR_SESSION_TOKEN", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Production" }), }); const key = await response.json(); const apiKey = key.fullKey; // Store apiKey in your secret manager; do not print it in logs. process.stdout.write(`Created API key ${key.id} (${key.prefix})\n`); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/api-keys", cookies={"session_token": "YOUR_SESSION_TOKEN"}, json={"name": "Production"}, ) key = response.json() api_key = key["fullKey"] # Store api_key in your secret manager; do not print it in logs. print(f"Created API key {key['id']} ({key['prefix']})") ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "log" "net/http" ) func main() { body, err := json.Marshal(map[string]string{"name": "Production"}) if err != nil { log.Fatal(err) } req, err := http.NewRequest("POST", "https://xquik.com/api/v1/api-keys", bytes.NewReader(body)) if err != nil { log.Fatal(err) } req.Header.Set("Content-Type", "application/json") req.AddCookie(&http.Cookie{Name: "session_token", Value: "YOUR_SESSION_TOKEN"}) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var key map[string]string if err := json.NewDecoder(resp.Body).Decode(&key); err != nil { log.Fatal(err) } apiKey := key["fullKey"] // Store apiKey in your secret manager; do not print it in logs. _ = apiKey fmt.Printf("Created API key %s (%s)\n", key["id"], key["prefix"]) } ``` | API key receipt column | Response source | Storage rule | | ---------------------- | --------------------------- | -------------------------------------------- | | Key ID | `id` | Use this ID for later revocation. | | Full API key | `fullKey` | Store it once in an approved secret manager. | | Safe prefix | `prefix` | Use this value in logs and key inventories. | | Key label | `name` | Identify the workload or environment. | | Creation time | `createdAt` | Record when credential access began. | | Active-key limit | `403 api_key_limit_reached` | Revoke an unused key before retrying. | ## Headers Dashboard session cookie. Format: `session_token=YOUR_SESSION_TOKEN`. Must be `application/json`. ## Body Display name for the key. Defaults to `"Default"` if omitted. ## Response ### 201 Created Unique identifier for the API key. The complete API key including the `xq_` prefix. First 8 characters of the key including the `xq_` prefix (e.g. `"xq_a1b2"`). Display name of the key. ISO 8601 creation timestamp. ```json theme={null} { "id": "42", "fullKey": "xq_YOUR_KEY_HERE", "prefix": "xq_a1b2", "name": "Production", "createdAt": "2026-02-24T10:30:00.000Z" } ``` The `fullKey` is returned **only once**. Store it securely. It cannot be retrieved again. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing, expired, or invalid dashboard session cookie. ### 403 Key limit reached ```json theme={null} { "error": "api_key_limit_reached", "limit": 100, "message": "API key limit reached. Delete an existing key first." } ``` You have reached the maximum number of active API keys (100). Revoke an active key before creating a new one. Revoked keys do not count toward the active limit. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. API key creation requires a same-origin dashboard session. API keys and OAuth bearer tokens cannot create additional keys. **Related:** [List API Keys](/api-reference/api-keys/list) · [Revoke API Key](/api-reference/api-keys/revoke) # X API Key Management: List Active Xquik Keys Source: https://docs.xquik.com/api-reference/api-keys/list GET /api-keys List Xquik API keys for X API authentication. Review each key ID, name, safe prefix, active state, creation time & last use before rotation or revocation. ```json theme={null} { "keys": [] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Audit Xquik API Keys Before X Automation List every Xquik key registered to the signed-in account. Review keys before running tweet searches, follower exports, webhooks, monitors, or X writes. This endpoint returns key inventory metadata. It never returns a complete API key. Use `id` for revocation and `prefix` for safe identification. The key list answers 5 operational questions: * Which named Xquik keys exist on this account? * Which keys are active or revoked? * When was each key created? * When did each key last authenticate a request? * Which key ID should a rotation workflow revoke? **Free** - does not consume credits ```bash cURL theme={null} curl https://xquik.com/api/v1/api-keys \ -H "Cookie: session_token=YOUR_SESSION_TOKEN" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/api-keys", { headers: { "Cookie": "session_token=YOUR_SESSION_TOKEN" }, }); const result = await response.json(); if (!response.ok) throw new Error(JSON.stringify(result)); const keyInventory = result.keys.map((key) => ({ id: key.id, name: key.name, safePrefix: key.prefix, active: key.isActive, createdAt: key.createdAt, lastUsedAt: key.lastUsedAt ?? null, })); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/api-keys", cookies={"session_token": "YOUR_SESSION_TOKEN"}, ) result = response.json() response.raise_for_status() key_inventory = [ { "id": key["id"], "name": key["name"], "safe_prefix": key["prefix"], "active": key["isActive"], "created_at": key["createdAt"], "last_used_at": key.get("lastUsedAt"), } for key in result["keys"] ] ``` ```go Go theme={null} package main import ( "fmt" "io" "log" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/api-keys", nil) if err != nil { log.Fatal(err) } req.AddCookie(&http.Cookie{Name: "session_token", Value: "YOUR_SESSION_TOKEN"}) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body)) } ``` ## Read the API Key Inventory Treat the response as an account credential inventory. Never treat it as a secret recovery endpoint. | Inventory check | Response field | Management decision | | ------------------- | -------------- | -------------------------------------------------- | | Stable key record | `id` | Pass this ID to the revoke endpoint. | | Workload label | `name` | Match the key to its service or environment. | | Safe identifier | `prefix` | Compare deployments without exposing the full key. | | Current state | `isActive` | Accept requests only when this value is `true`. | | Credential age | `createdAt` | Review old keys against your rotation policy. | | Last authentication | `lastUsedAt` | Investigate stale or unexpectedly active keys. | `lastUsedAt` is optional. Its absence means no authenticated use was recorded. It does not prove that a deployment no longer needs the key. Check scheduled jobs before revoking a quiet key. A monthly follower export can remain valid without recent requests. Confirm the owner and workload first. ## Distinguish Xquik Keys From Official X Credentials An Xquik API key starts with `xq_`. It authenticates requests to Xquik routes. It is not an official Twitter API key or X developer bearer token. | Credential | Purpose | Where to manage it | | ----------------------- | ----------------------------------------------------------------------- | -------------------------- | | Xquik API key | Authenticate Xquik tweet, follower, monitor, webhook, and write routes. | Xquik API key pages. | | Official X API key | Identify an application on the X developer platform. | X developer console. | | Xquik dashboard session | Authorize Xquik API key management. | Signed-in Xquik dashboard. | Official X documentation separates application keys from bearer tokens. Read its [authentication overview](https://docs.x.com/fundamentals/authentication/oauth-1-0a/api-key-and-secret) when integrating directly with the X developer platform. Use [Xquik authentication](/api-reference/authentication) for Xquik request headers. Use this list endpoint only for Xquik credential inventory. ## Rotate an Xquik API Key Safely Create the replacement before revoking the current key. This overlap prevents failed tweet, follower, webhook, and monitor requests. 1. List keys and record the current `id`, `name`, and `prefix`. 2. [Create a replacement key](/api-reference/api-keys/create) with a clear name. 3. Store the returned `fullKey` in an approved secret manager. 4. Update one deployment without logging the replacement value. 5. Send a planned Xquik request from that deployment. 6. List keys and verify the replacement `lastUsedAt` value. 7. [Revoke the old key](/api-reference/api-keys/revoke) by its exact `id`. 8. List keys again and confirm the old key is inactive. OWASP treats creation, rotation, revocation, and expiration as a secret lifecycle. Review its [secrets management guidance](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html) when defining your organization policy. The Xquik list response does not expose scopes or expiration fields. Do not invent those controls from names or prefixes. Use `isActive` as the documented key state. ## Answer Common X API Key Questions ### Can I Recover My Full Xquik API Key? No. `GET /api-keys` returns only a safe prefix. The creation response returns `fullKey` once. Create a replacement when the stored secret is unavailable. ### Can an API Key List Other Xquik Keys? No. This management endpoint requires a same-origin dashboard session. An `x-api-key` header or OAuth bearer token cannot authorize the request. ### How Do I Check Which Xquik Key Is Active? Match the deployed key prefix with `prefix`. Then check `isActive`. Never print the complete deployed key during this comparison. ### What Does a Missing Last-Used Time Mean? The key has no recorded authenticated request. It may be new or unused. Check the intended workload before revocation. ### Does This Endpoint List Official Twitter API Keys? No. It lists Xquik keys for Xquik endpoints. Manage official X developer credentials in the X developer console. ## Headers Dashboard session cookie. Format: `session_token=YOUR_SESSION_TOKEN`. ## Response ### 200 OK Array of API key objects. **Key object fields:** Unique identifier for the API key. Display name of the key. First 8 characters of the key including the `xq_` prefix (e.g. `"xq_a1b2"`). Whether the key is currently active. ISO 8601 creation timestamp. ISO 8601 timestamp of the last API call made with this key. Omitted if never used. ```json theme={null} { "keys": [ { "id": "42", "name": "Production", "prefix": "xq_a1b2", "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "lastUsedAt": "2026-02-24T18:45:12.000Z" }, { "id": "43", "name": "Staging", "prefix": "xq_c3d4", "isActive": true, "createdAt": "2026-02-20T09:00:00.000Z" } ] } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing, expired, or invalid dashboard session cookie. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. API key listing requires a same-origin dashboard session. API keys and OAuth bearer tokens cannot list account keys. **Related:** [Create API Key](/api-reference/api-keys/create) · [Revoke API Key](/api-reference/api-keys/revoke) # Revoke Xquik API Key & Stop Request Access Source: https://docs.xquik.com/api-reference/api-keys/revoke DELETE /api-keys/{id} Permanently revoke an API key used for tweet, follower, profile, monitor, webhook, export, or account-action requests. Revocation is immediate. See costs. ```json theme={null} { "success": true } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/api-keys/42 \ -H "Cookie: session_token=YOUR_SESSION_TOKEN" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/api-keys/42", { method: "DELETE", headers: { "Cookie": "session_token=YOUR_SESSION_TOKEN" }, }); const result = await response.json(); console.log(result); ``` ```python Python theme={null} import requests response = requests.delete( "https://xquik.com/api/v1/api-keys/42", cookies={"session_token": "YOUR_SESSION_TOKEN"}, ) result = response.json() print(result) ``` ```go Go theme={null} package main import ( "fmt" "io" "log" "net/http" ) func main() { req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/api-keys/42", nil) if err != nil { log.Fatal(err) } req.AddCookie(&http.Cookie{Name: "session_token", Value: "YOUR_SESSION_TOKEN"}) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body)) } ``` ## Path parameters The unique identifier of the API key to revoke. ## Headers Dashboard session cookie. Format: `session_token=YOUR_SESSION_TOKEN`. ## Response ### 200 OK Always `true` when the key is successfully revoked. ```json theme={null} { "success": true } ``` ### 400 Invalid ID ```json theme={null} { "error": "invalid_id" } ``` The path parameter is not a valid ID. IDs are numeric strings. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing, expired, or invalid dashboard session cookie. ### 404 Not Found ```json theme={null} { "error": "not_found" } ``` No API key with this ID exists on your account, or it has already been revoked. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. Revocation is **immediate and permanent**. All requests using the revoked key will return `401 Unauthenticated`. This action cannot be undone. Create a new key if needed. API key revocation requires a same-origin dashboard session. API keys and OAuth bearer tokens cannot revoke account keys. **Related:** [Create API Key](/api-reference/api-keys/create) · [List API Keys](/api-reference/api-keys/list) # X API Key Authentication for Tweets & Webhooks Source: https://docs.xquik.com/api-reference/authentication Authenticate tweets, follower exports, monitors, webhooks, and X writes with Xquik API keys, guest keys, OAuth 2.1, sessions, or MPP. See request fields.
For the complete documentation index, see llms.txt.
Authenticate tweet, follower, monitor, webhook, and write requests with an Xquik API key. This REST API authentication guide also covers guest keys, OAuth 2.1, sessions, and MPP. Xquik keys authenticate Xquik endpoints only. They are not an official Twitter API key or X API token. Choose the narrowest method that covers the route. ## API Key Format Account and guest keys follow this format: ```text theme={null} xq_your_api_key_here ``` * **Prefix:** `xq_` * **Body:** 64 hexadecimal characters * **Storage:** Keep each REST API key in a secret manager. Never log or commit it. ## Using your API key Send account API requests with the `x-api-key` request header: ```bash cURL theme={null} curl https://xquik.com/api/v1/account \ -H "x-api-key: xq_your_api_key_here" ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/account", { headers: { "x-api-key": "xq_your_api_key_here" }, }); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/account", headers={"x-api-key": "xq_your_api_key_here"}, ) ``` ```go Go theme={null} package main import ( "fmt" "io" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://xquik.com/api/v1/account", nil) req.Header.Set("x-api-key", "xq_your_api_key_here") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` For Bearer tokens, send the same key in the authorization header: ```text theme={null} Authorization: Bearer xq_your_api_key_here ``` Use Bearer authentication for guest keys. An `xq_` value remains an Xquik API key. OAuth 2.1 access tokens omit the `xq_` prefix. ## Auth methods by endpoint This access control matrix maps each API endpoint to its accepted method. Most routes accept an API key or dashboard session cookie. `GET /account` accepts `x-api-key`. Use it to check plan, credit balance, and monitor billing from server-side integrations. `POST /api-keys`, `GET /api-keys`, and `DELETE /api-keys/{id}` require a dashboard session cookie. The session identifies the authenticated user. `PATCH /account`, `PUT /account/x-identity`, and `POST /subscribe` accept either `x-api-key` or a dashboard session cookie. `* /monitors/*`, `GET /events/*`, `* /webhooks/*`, and `GET /webhooks/{id}/deliveries` accept either auth method. `* /draws/*`, `* /extractions/*`, `* /x/*`, `POST /x/media/download`, `* /x-accounts/*`, and `* /x-write/*` accept either auth method. `GET /trends`, `GET /radar`, `* /styles/*`, `* /drafts/*`, `POST /compose`, and `* /support/*` accept either auth method. A guest key has scope `paid_reads`. It authenticates only the 33 prepaid GET routes plus guest status and top-up. It never grants account, write, automation, account credential, or OAuth access. API key creation, listing, and revocation require a same-origin dashboard session. API keys and OAuth bearer tokens cannot manage API keys. The MCP server also supports OAuth 2.1 with PKCE for browser-based clients. See [OAuth 2.1](/oauth/overview) for current client compatibility and setup. ## Accountless guest keys After the user confirms $10-$250 USD, `POST /api/v1/guest-wallets` returns a one-use hosted checkout, a guest key, and a status URL without charging. The guest key remains inactive for paid reads until payment is verified. It can authenticate `GET /api/v1/guest-wallets/status` while pending. Once active, it can call exactly the [33 eligible paid-read routes](/guides/guest-wallets#eligible-paid-read-routes). Never create a guest wallet or top-up automatically after a `401` or `402`. Ask the user to choose an amount and explicitly confirm. The user must open and complete the hosted checkout. Guest credential routes are direct REST only. The API MCP server never exposes wallet creation, status, or top-up as executable operations. See [Accountless guest wallets](/guides/guest-wallets) for the complete flow. ## Machine Payments Protocol Seven fixed-price reads also accept direct [MPP](/mpp/machine-payments-protocol) payments. This replaces API key authentication for those reads. Without credentials, the API server returns a 402 payment challenge. ### Challenge header ```text theme={null} WWW-Authenticate: Payment id="challenge_id_here", realm="xquik.com", method="tempo", intent="charge", request="payment_request_here" ``` | Parameter | Description | | --------- | ----------------------------------------------------------- | | `id` | Unique challenge identifier | | `realm` | Protection space (`xquik.com`) | | `method` | Payment method (`tempo`) | | `intent` | Payment intent (`charge`) | | `request` | Base64url-encoded JSON with amount, currency, and recipient | ### Credential header After completing the payment, retry the request with a payment credential: ```text theme={null} Authorization: Payment ``` The credential contains the original challenge parameters and a method-specific payload proving payment. ### Receipt header Settled responses include a receipt: ```text theme={null} Payment-Receipt: ``` The receipt confirms settlement with a reference ID and timestamp. Every response after accepted payment includes this header, including non-2xx responses. Check the HTTP status and response body. Confirm application success before processing the result. ### Eligible endpoints See the [MPP overview](/mpp/machine-payments-protocol#eligible-endpoints) for all 7 direct MPP operations and fixed prices. The 26 non-MPP paid reads return `401` with `WWW-Authenticate: Bearer` and the optional guest wallet action. The 7 direct MPP operations return `402` with `WWW-Authenticate: Payment` and the same guest action. A failed read never creates checkout. ## Key management ### Create a Key Generate keys from the **API Keys** page in your dashboard or via the API (session auth only): ```bash Create API Key theme={null} curl -X POST https://xquik.com/api/v1/api-keys \ -H "Cookie: session_token=your_session_token_here" \ -H "Content-Type: application/json" \ -d '{"name": "Production"}' ``` The full key (`fullKey`) is returned **once** in the creation response. Store it securely. ### Revoke a Key ```bash Revoke API Key theme={null} curl -X DELETE https://xquik.com/api/v1/api-keys/123 \ -H "Cookie: session_token=your_session_token_here" ``` Revoked keys are deactivated immediately and cannot be reactivated. ## Error response Invalid or missing API key returns: ```json theme={null} { "error": "unauthenticated" } ``` **Status:** `401 Unauthorized` The API blocks unauthorized access. Replace the credential before retrying API access. ## Security best practices Apply these API security controls to every environment. Never hardcode API keys in your source code. Use environment variables to keep keys separate from your codebase: ```bash .env theme={null} XQUIK_API_KEY=xq_your_api_key_here ``` Access the key in your application: ```javascript Node.js theme={null} const apiKey = process.env.XQUIK_API_KEY; ``` ```python Python theme={null} import os api_key = os.environ["XQUIK_API_KEY"] ``` ```go Go theme={null} apiKey := os.Getenv("XQUIK_API_KEY") ``` Add `.env` to your `.gitignore` to prevent accidental commits: ```bash .gitignore theme={null} # Environment variables .env .env.local .env.production ``` If a key is accidentally committed, revoke it immediately from your dashboard and generate a new one. Consider the exposed key compromised even if you force-push to remove it from history. Rotate API keys periodically to limit the impact of a potential leak: 1. Create a new key from the dashboard 2. Update the key in all your environments 3. Verify all services work with the new key 4. Revoke the old key Xquik supports multiple active keys, so you can rotate without downtime. Create distinct API keys for each environment. This limits blast radius if a development key is compromised and makes it easier to track usage per environment: ```bash .env.local theme={null} # Development XQUIK_API_KEY=xq_dev_key_here ``` ```bash .env.production theme={null} # Production XQUIK_API_KEY=xq_prod_key_here ``` Name your keys descriptively (e.g., "Production - Backend", "Staging", "Local Dev") so you can identify them in the dashboard. **Next steps:** [Quickstart](/x-api-quickstart) for a complete setup walkthrough, or [OAuth Overview](/oauth/overview) for OAuth 2.1 integration. # Tweet Composer API, Writing Rules & 9 Draft Checks Source: https://docs.xquik.com/api-reference/compose/create POST /compose Plan an X post, refine it by goal and voice, then check links, hashtags, capitalization, length, punctuation, emojis, dashes, substance, and repeated URLs. ```json theme={null} { "checklist": [ { "factor": "No external links in body", "passed": true } ], "nextStep": "All 9 checks passed. Get an account from GET /api/v1/x/accounts. Then send the draft to POST /api/v1/x/tweets. The intentUrl also supports one-click posting.\n", "passed": true, "passedCount": 9, "topSuggestion": "All Xquik editorial checks passed.", "totalChecks": 9, "intentUrl": "https://x.com/intent/tweet?text=PostgreSQL%2018%20reduced%20query%20latency" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free.** This endpoint does not consume credits. Use this guided Tweet composer to plan, refine, and check one post draft. The endpoint returns questions, writing rules, patterns, and deterministic checks. It does not generate final Tweet text. It also never publishes a post. This boundary separates the endpoint from an automatic Tweet generator. Use 3 Compose calls for one complete writing cycle. ## Workflow 1. Call `compose` with a topic. 2. If fresh context helps, fetch one suggested `radarRecommendations` endpoint. 3. Pass selected facts to `refine` with the topic, goal, and tone. 4. Write a draft. Call `score` with the full text. The guidance uses Xquik editorial heuristics. It does not predict reach. X does not publish its production ranking weights. ## Choose the Correct Tweet Writing Step | Step | Required input | Returned writing help | Not returned | | --------- | ----------------------- | ---------------------------------------------- | ------------------- | | `compose` | `topic` | 18 rules, 4 questions, 7 Radar suggestions | Finished Tweet text | | `refine` | `topic`, `goal`, `tone` | Goal guidance and 3 structural patterns | Finished Tweet text | | `score` | `draft` | 9 checks, failure suggestions, and pass status | Predicted reach | Each call returns only its step-specific shape. Do not deserialize every result into one generic response type. ## Match the Goal to Your Tweet Intent The `goal` changes rule order, questions, and refine patterns. It does not guarantee likes, replies, reposts, profile visits, or followers. | Goal | Compose question | Refine emphasis | | -------------- | -------------------------------------------- | --------------------------------------------------- | | `engagement` | What call to action should readers take? | One useful point and concrete example | | `followers` | Why should readers follow this account? | Niche expertise and future value | | `authority` | What expertise or insight supports the post? | Evidence, reasoning, and useful frameworks | | `conversation` | What replies should the post invite? | Context, one open question, and readable paragraphs | `compose` defaults to `engagement` when `goal` is omitted. Send one documented goal explicitly when downstream behavior must remain stable. ## Requests Returns 18 editorial rules, 4 follow-up questions, and 7 source-specific Radar recommendations. ```bash cURL theme={null} curl https://xquik.com/api/v1/compose \ -X POST \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "step": "compose", "topic": "PostgreSQL query planning", "goal": "authority" }' ``` Required fields: `step`, `topic`. Optional fields: * `goal`: `engagement`, `followers`, `authority`, or `conversation`. Defaults to `engagement`. * `styleUsername`: An analyzed X username or saved custom style label. Returns goal, tone, media, and editorial guidance. ```bash cURL theme={null} curl https://xquik.com/api/v1/compose \ -X POST \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "step": "refine", "topic": "PostgreSQL query planning", "goal": "authority", "tone": "professional", "mediaType": "none" }' ``` Required fields: `step`, `topic`, `goal`, `tone`. Optional fields: * `mediaType`: `photo`, `video`, or `none`. * `callToAction`: Specific action the draft should request. * `additionalContext`: Audience, constraints, or source context. Runs 9 deterministic text checks. ```bash cURL theme={null} curl https://xquik.com/api/v1/compose \ -X POST \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "step": "score", "draft": "PostgreSQL 18 reduced query latency by 30%. Test the JIT compiler on analytical workloads. Which result changed your rollout plan?", "hasLink": false }' ``` Required fields: `step`, `draft`. `hasLink` means a separate link card is attached. The deprecated `hasMedia` field remains accepted. Text checks ignore it. ## Refine Tone and Media Guidance The `tone` field accepts any non-empty string. Known terms receive focused guidance. Other terms receive general audience and consistency guidance. | Tone words | Returned emphasis | | ---------------------------- | ----------------------------------------- | | `casual`, `conversational` | Direct language and familiar words | | `professional`, `formal` | Precise terms and complete sentences | | `provocative`, `bold` | Challenge claims without attacking people | | `humor`, `witty`, `funny` | Keep the joke clear and useful | | `educational`, `informative` | Define terms and order complex points | | Any other non-empty value | Match audience expectations consistently | `mediaType` accepts `photo`, `video`, or `none`. Photo guidance covers context, mobile cropping, and alt text. Video guidance covers early value and captions. Text-only guidance never requires unnecessary media. ## Headers Send `x-api-key` with an Xquik API key. OAuth clients can send a bearer token through the `Authorization` header instead. Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). OAuth bearer token using `Bearer YOUR_TOKEN`. Use `application/json`. ## Body Use exactly `compose`, `refine`, or `score`. Non-empty subject. Required for `compose` and `refine`. Use `engagement`, `followers`, `authority`, or `conversation`. Required for `refine`. Optional for `compose`. Account-scoped analyzed username or custom label. Only used by `compose`. Xquik lowercases the lookup value. Non-empty voice description. Required for `refine`. Optional `photo`, `video`, or `none` value for `refine`. Optional requested action for `refine`. Optional audience, constraint, or verified source context for `refine`. Include selected Radar facts only when current context helps. Non-empty full post text. Required for `score`. Set true when a separate link card is attached during `score`. Deprecated compatibility field. Text checks ignore it. Unknown body fields are ignored. Send only the fields for the selected step. ## Response ### 200 OK The response shape matches the requested workflow step. ## Compose Response 18 Xquik editorial rules. Each item has `rule`. One concrete editorial rule for the requested goal. 4 questions for the next writing step. 7 source-specific suggestions for researching a fresh post angle. Radar endpoint for the suggested source. Source-specific instructions for using returned facts. Radar source identifier. Current-topic research that the source supports. 19 published signal names. Every `multiplier` states that X does not publish the production weight. Human-readable public signal name. States that X does not publish the production multiplier. 19 published signal names. Every `weight` is `null`. Signal name from X's public ranking repository. Signal direction and the limit of public evidence. Always `null` because X does not publish production weights. States that X publishes no universal engagement window or decay rate. 4 negative predictions named by X's public model. No severity order is claimed. Signal source and evidence limits. X post intent seeded with the topic. Optional Radar research guidance and exact fields required for `refine`. Saved styles. Present when saved styles exist and `styleUsername` is omitted. Saved analyzed username or custom style label. Cached post count for the style. Cached examples. Present when `styleUsername` matches a cached style. Fallback instruction when the requested style is unavailable. ### Match a Saved Twitter Writing Style `styleUsername` searches only styles owned by the authenticated account. Xquik lowercases the lookup value before searching. | Style lookup | Result | | ------------------------------------------ | ---------------------------------------------- | | Matching analyzed username or custom label | 200 response with `styleTweets` | | Omitted value with saved styles | 200 response with `savedStyles` | | Missing style without available credits | 200 response with `styleNote` | | Missing style with available credits | 400 `invalid_input` with analysis instructions | The 200 fallback still returns the complete Compose response. The 400 branch returns only the documented error object. Analyze the username first when the account can run a new style analysis. ## Refine Response Goal, tone, media, and editorial guidance. 3 patterns. Each item has `description` and `pattern`. Explains the purpose of one structure. Lists the ordered writing blocks without generating final text. X post intent seeded with the topic. Exact fields required for the `score` call. ## Score Response Runs 9 deterministic text checks. | Check | Passing rule | Fix for a failed check | | -------------- | ------------------------------------------------------------------- | ------------------------------------------------ | | External links | No `http`, `https`, `t.co`, or `www` URL appears | Move the URL to a reply or skip this style check | | Hashtags | No hashtag remains after URLs are removed | Remove hashtags or skip this style check | | Capitalization | Uppercase letters are at most 30% of letters | Replace all-caps wording with normal casing | | Length | The draft contains 50 through 280 characters | Add useful context or trim the draft | | Punctuation | No run contains 4 consecutive `!` or `?` marks | Use 3 or fewer consecutive marks | | Emojis | No extended pictographic emoji appears | Remove emojis or skip this style check | | Dashes | No em dash, en dash, or double dash appears | Use commas, periods, colons, or parentheses | | Substance | At least 8 words remain after removing URLs, hashtags, and mentions | Add a concrete claim, example, or takeaway | | Repeated URL | A separate link card does not duplicate a body URL | Remove either the body URL or link card | These are Xquik style checks. They are not X ranking guarantees. Some failures can reflect an intentional editorial choice. The endpoint has no per-check disable switch. Review the failed factor before changing valid copy. Treat "skip this style check" as an editorial exception. The API still marks that factor as failed. ```json theme={null} { "checklist": [ { "factor": "No external links in body", "passed": true }, { "factor": "No hashtags", "passed": true }, { "factor": "No excessive capitalization", "passed": true }, { "factor": "Length between 50 and 280 characters", "passed": true }, { "factor": "No excessive punctuation", "passed": true }, { "factor": "No emojis", "passed": true }, { "factor": "No em dashes or double dashes", "passed": true }, { "factor": "Sufficient substance", "passed": true }, { "factor": "Link-in-reply strategy", "passed": true } ], "intentUrl": "https://x.com/intent/tweet?text=...", "nextStep": "All 9 checks passed. Get an account from GET /api/v1/x/accounts. Then send the draft to POST /api/v1/x/tweets. The intentUrl also supports one-click posting.", "passed": true, "passedCount": 9, "topSuggestion": "All Xquik editorial checks passed.", "totalChecks": 9 } ``` `intentUrl` appears only when all 9 checks pass. Failed items include a `suggestion`. Exactly 9 deterministic editorial checks. Stable name for the evaluated rule. Whether the draft satisfies this rule. Revision guidance. Present only when this check fails. True only when all 9 checks pass. Number of passing checks from 0 through 9. Always `9` for the current contract. Highest-priority failed suggestion, or the all-passed message. X post intent containing the draft. Present only when every check passes. Publishing instructions after success. Revision instructions after failure. ## Errors | Status | Cause | Fix | | ------ | ------------------------------------------------ | ----------------------------------------------- | | `400` | Invalid `step` or non-object JSON body | Send `compose`, `refine`, or `score` | | `400` | Missing `topic` for `compose` | Send a non-empty topic | | `400` | Missing `goal`, `tone`, or `topic` for `refine` | Send every required Refine field | | `400` | Missing `draft` for `score` | Send the complete post text | | `400` | Account with credits requested an uncached style | Analyze the username with `POST /api/v1/styles` | | `401` | Missing or invalid API credentials | Send an API key or OAuth bearer token | | `429` | Request rate exceeded | Wait for `Retry-After` before retrying | ```json theme={null} { "error": "invalid_input", "message": "step is required. Must be \"compose\", \"refine\", or \"score\"." } ``` Missing step-specific fields also return `invalid_input`. ```json theme={null} { "error": "unauthenticated" } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for `Retry-After` before retrying. ## Tweet Composer Questions ### What Does the Tweet Composer Generate? It generates writing rules, research prompts, structural patterns, and checks. It never returns finished Tweet text. You write the draft between `refine` and `score`. This keeps facts, opinions, and account voice under your control. ### Is This Endpoint a Tweet Generator? No. A Tweet generator usually writes final post text from one prompt. This endpoint guides a deliberate writing workflow. It helps plan, refine, and check one draft without publishing it. ### How Does It Help Write a Good Tweet? Choose a goal before drafting. Add verified context when the topic needs it. Use the returned pattern as structure, not final copy. Then run all 9 checks. Revise failed factors only when the rule matches your editorial policy. ### Can It Supply Current Twitter Post Ideas? The Compose response lists 7 Radar sources. Each suggestion includes an endpoint, source, purpose, and usage guidance. Fetch only relevant sources. Place selected facts in `additionalContext` during Refine. Radar supplies research leads, not verified claims. Check publication dates, source credibility, and original context before writing. ### Can It Match a Saved Twitter Writing Style? Yes. Send an analyzed username or custom style label in `styleUsername`. A successful match returns cached `styleTweets`. Use those samples to study sentence length, vocabulary, openings, and calls to action. The endpoint does not rewrite the draft automatically. It also does not verify that one style belongs to a public X username. ### What Happens When a Draft Check Fails? `passed` becomes false. The failed checklist items receive `suggestion` text. `topSuggestion` selects the highest-priority revision. `intentUrl` stays absent until all 9 checks pass. ### Does the Score Predict Likes, Replies, or Reposts? No. The score applies Xquik editorial rules only. It cannot predict likes, replies, reposts, bookmarks, profile visits, or follower growth. X does not publish the production ranking weights required for such a prediction. ### Can the Endpoint Publish the Draft? No. Use [Create Tweet](/api-reference/x-write/create-tweet) after review. You can also open `intentUrl` after every score check passes. Research current context with [Radar](/api-reference/radar/list). Save accepted text with [Create Draft](/api-reference/drafts/create). Analyze a reference voice with [Analyze Style](/api-reference/styles/analyze). # Xquik Credit Balance, API Usage & Billing Data Source: https://docs.xquik.com/api-reference/credits/get GET /credits Retrieve available API credits, included monthly credits, purchased credits, lifetime usage totals, and automatic top-up settings. See request fields. ```json theme={null} { "auto_topup_amount_dollars": 10, "auto_topup_enabled": false, "auto_topup_threshold": "50000", "balance": "50000", "lifetime_purchased": "200000", "lifetime_used": "150000" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Choose A Billing Checkpoint Use this route for a read-only billing checkpoint. It returns current balance, lifetime totals, and automatic top-up settings. Use purchase and status routes only after deciding to add credits. ## Check Credits Before Tweets, Followers, or Monitors Read the current balance before starting a large tweet search, follower export, reply export, active monitor, or X write. Compare the balance with the route's documented credit cost. Reduce the requested result count when the available balance cannot cover the full job. Keep `balance`, `lifetime_purchased`, and `lifetime_used` as strings. These values can exceed JavaScript's safe integer range. Convert them with a bigint library only when the client supports exact arithmetic. Review automatic top-up fields separately. `auto_topup_enabled` reports whether automatic funding is active. The threshold and dollar amount describe when and how much the account adds. This endpoint never charges a payment method. Use standard top-up for a hosted checkout. Use quick top-up only after the account has a saved payment method. Poll standard checkout status with the checkout session ID. **Free** - does not consume credits ```bash cURL theme={null} curl https://xquik.com/api/v1/credits \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/credits", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/credits", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() print(data) ``` ## Headers Your API key. Session cookie authentication is also supported. ## Response ### 200 OK Dollar amount charged when automatic top-up runs. Whether automatic top-up is enabled. Credit balance threshold that triggers automatic top-up when enabled (Bigint string). Current credit balance (Bigint string to preserve precision above Number.MAX\_SAFE\_INTEGER). Total credits purchased (Bigint string). Total credits consumed (Bigint string). ```json theme={null} { "auto_topup_amount_dollars": 10, "auto_topup_enabled": false, "auto_topup_threshold": "50000", "balance": "450", "lifetime_purchased": "1000", "lifetime_used": "550" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [Top Up Credits](/api-reference/credits/topup) · [Get Top-Up Status](/api-reference/credits/topup-status) · [Quick Top-Up](/api-reference/credits/quick-topup) · [Billing Guide](/guides/billing) # Twitter API Billing: Instant X API Credit Top-up Source: https://docs.xquik.com/api-reference/credits/quick-topup POST /credits/quick-topup Charge a saved payment method for USD 10-500 of tweet, profile, follower, monitor, webhook, export, and X write API credits. Includes response fields. ```json theme={null} { "outcome": "charged", "balance": "1450", "credits": "1000" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
Add X API credits for tweet, follower, monitor, webhook, export, and write calls. Use this Twitter API billing route with an existing saved payment method. ## Add API Credits With a Saved Payment Method Use quick top-up when the account already has a reusable payment method. Send a USD amount from 10 through 500. The route funds tweet, profile, follower, monitor, webhook, export, and X write requests without creating a hosted checkout page. Read `outcome` before handling any other response field. `charged` means the credits and new balance are ready. `requires_action` means the client must complete the payment confirmation flow. Another outcome can require adding a payment method first. Never print or persist `clientSecret`. Pass it directly to the payment confirmation flow. Log only the outcome, added credits, and resulting balance after a successful charge. Quick top-up differs from standard checkout status. The status route polls a hosted checkout session by `session_id`. This route returns its own immediate payment outcome for the saved payment method. **Free** - does not consume credits ```bash cURL theme={null} response=$(curl -sS -X POST https://xquik.com/api/v1/credits/quick-topup \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{"dollars": 25}') outcome=$(jq -r '.outcome' <<<"$response") if [ "$outcome" = "requires_action" ]; then client_secret=$(jq -r '.clientSecret' <<<"$response") # Pass $client_secret to the billing confirmation flow; do not print it. elif [ "$outcome" = "charged" ]; then jq '{outcome, credits, balance}' <<<"$response" else jq '{outcome}' <<<"$response" fi ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/credits/quick-topup", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ dollars: 25 }), }); const result = await response.json(); if (result.outcome === "requires_action") { const paymentClientSecret = result.clientSecret; // Pass paymentClientSecret to the billing confirmation flow; do not print it. } else if (result.outcome === "charged") { process.stdout.write(`Added ${result.credits} credits. Balance: ${result.balance}\n`); } else { process.stdout.write("Add a payment method before quick top-up.\n"); } ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/credits/quick-topup", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"dollars": 25}, ) data = response.json() if data["outcome"] == "requires_action": payment_client_secret = data["clientSecret"] # Pass payment_client_secret to the billing confirmation flow; do not print it. elif data["outcome"] == "charged": print(f"Added {data['credits']} credits. Balance: {data['balance']}") else: print("Add a payment method before quick top-up.") ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "dollars": 25, }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/credits/quick-topup", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } outcome, _ := data["outcome"].(string) if outcome == "requires_action" { clientSecret, _ := data["clientSecret"].(string) // Pass clientSecret to the billing confirmation flow; do not print it. _ = clientSecret } else if outcome == "charged" { credits, _ := data["credits"].(string) balance, _ := data["balance"].(string) fmt.Printf("Added %s credits. Balance: %s\n", credits, balance) } else { fmt.Println("Add a payment method before quick top-up.") } } ``` | Credit top-up outcome | Response fields | Next action | | ---------------------- | ------------------------------ | ---------------------------------------------- | | Immediate charge | `outcome: "charged"` | Store `credits` and the updated `balance`. | | Payment authentication | `outcome: "requires_action"` | Complete the billing confirmation flow. | | Missing payment method | `outcome: "no_payment_method"` | Create a checkout top-up instead. | | Added credits | `credits` | Treat the bigint value as a string. | | Updated balance | `balance` | Gate the next tweet, follower, or monitor job. | | Confirmation secret | `clientSecret` | Send it only to the billing confirmation flow. | ## Headers Your API key. Session cookie authentication is also supported. Must be `application/json`. ## Body Amount in US dollars to charge. Minimum $10, maximum $500. At USD 0.00015 per credit, a USD 25 quick top-up adds 166,666 credits, rounded down to whole credits. Only the `charged` outcome grants credits and updates `balance`. If the endpoint returns `requires_action`, complete payment authentication with `clientSecret` before retrying the metered API call. Pass `clientSecret` to the billing confirmation flow only; do not print it in logs. If it returns `no_payment_method`, create a checkout top-up instead. ## Response ### 200 Charged Payment succeeded immediately. Credits added to your balance. Always `"charged"`. Updated credit balance after top-up (Bigint string). Number of credits added (Bigint string). ```json theme={null} { "outcome": "charged", "balance": "466666", "credits": "166666" } ``` ### 200 Requires action Payment requires additional authentication (e.g., 3D Secure). Use the returned client secret with the billing confirmation flow to complete the payment. Always `"requires_action"`. Payment client secret for completing the payment. ```json theme={null} { "outcome": "requires_action", "clientSecret": "pi_3abc...secret_xyz" } ``` ### 200 No payment method No saved payment method on file. Redirect the user to add one via the billing portal, or use the standard [top-up endpoint](/api-reference/credits/topup) instead. Always `"no_payment_method"`. ```json theme={null} { "outcome": "no_payment_method" } ``` ### 400 Invalid input ```json theme={null} { "error": "Invalid input" } ``` The request body is missing a numeric `dollars` value or includes too many decimal places. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [Top Up Credits](/api-reference/credits/topup) · [Get Top-Up Status](/api-reference/credits/topup-status) · [Get Credits](/api-reference/credits/get) · [Billing Guide](/guides/billing) # Buy X API Credits for Tweets, Followers & Writes Source: https://docs.xquik.com/api-reference/credits/topup POST /credits/topup Create a hosted checkout for at least USD 10 of tweet, profile, follower, monitor, webhook, export, and X write API credits. Includes response fields. ```json theme={null} { "redirect_url": "https://xquik.com/api/v1/credits/topup/redirect?session_id=checkout_example", "url": "https://xquik.com/api/v1/credits/topup/redirect?session_id=checkout_example" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Choose Hosted Checkout Use this route when the buyer needs a hosted checkout. Use quick top-up for a saved payment method. Use top-up status only to poll an existing checkout session. Store the checkout reference before redirecting the user. **Free** - does not consume credits Ask the user to confirm the dollar amount before calling this endpoint. The response creates a hosted checkout redirect. It does not complete payment or add credits by itself. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/credits/topup \ -H "x-api-key: xq_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"dollars": 10}' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/credits/topup", { method: "POST", headers: { "x-api-key": "xq_your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ dollars: 10 }), }); const data = await response.json(); process.stdout.write(`${JSON.stringify(data, null, 2)}\n`); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/credits/topup", headers={"x-api-key": "xq_your_api_key_here"}, json={"dollars": 10}, ) data = response.json() print(data) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "dollars": 10, }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/credits/topup", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_your_api_key_here") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Headers Your API key. Session cookie authentication is also supported. Must be `application/json`. ## Body Amount in US dollars to top up. Minimum \$10. Optional checkout locale. Defaults to `en`. ## Response ### 200 OK Stable Xquik redirect URL for the active hosted checkout session. Same stable redirect URL in snake\_case. ```json theme={null} { "url": "https://xquik.com/api/v1/credits/topup/redirect?session_id=checkout_session_id", "redirect_url": "https://xquik.com/api/v1/credits/topup/redirect?session_id=checkout_session_id" } ``` ### 400 Invalid input ```json theme={null} { "error": "Minimum top-up is $10" } ``` The top-up amount is below the \$10 minimum. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [Get Credits](/api-reference/credits/get) · [Get Top-Up Status](/api-reference/credits/topup-status) · [Quick Top-Up](/api-reference/credits/quick-topup) · [Billing Guide](/guides/billing) # Check X API Credit Top-up Payment Status API Source: https://docs.xquik.com/api-reference/credits/topup-status GET /credits/topup/status Poll a credit checkout session for pending, paid, expired, or failed status before starting tweet, follower, monitor, or write requests. See examples. ```json theme={null} { "amount_dollars": 25, "credits": "166666", "status": "paid" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Use this endpoint after creating a standard [top-up checkout](/api-reference/credits/topup). Pass the checkout session ID for that top-up to check whether payment is complete. ```bash cURL theme={null} curl "https://xquik.com/api/v1/credits/topup/status?session_id=checkout_session_id" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const sessionId = "checkout_session_id"; const response = await fetch( `https://xquik.com/api/v1/credits/topup/status?session_id=${encodeURIComponent(sessionId)}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/credits/topup/status", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"session_id": "checkout_session_id"}, ) data = response.json() print(data) ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" "net/url" ) func main() { endpoint, err := url.Parse("https://xquik.com/api/v1/credits/topup/status") if err != nil { panic(err) } query := endpoint.Query() query.Set("session_id", "checkout_session_id") endpoint.RawQuery = query.Encode() req, err := http.NewRequest("GET", endpoint.String(), nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Gate Queued API Work on the Checkout Result Store the checkout session ID when creating a standard top-up. Associate it with the intended tweet, follower, reply, monitor, or write workload. Never substitute a session from another account or purchase. Poll this endpoint before releasing credit-dependent work. Interpret the returned status as a state machine: * `processing` keeps the workload paused. * `paid` allows a fresh credit-balance check. * `failed` requires a new checkout. * `expired` requires a new checkout. Do not start work from `amount_dollars` or `credits` alone. Only `paid` confirms that credits were granted. Then call [Get Credits](/api-reference/credits/get). Compare the balance with the planned request cost. Keep processing polls bounded. Wait between requests and honor `Retry-After` after rate limiting. Several rapid polls cannot accelerate the payment result. Store the last observed status and check time with the session ID. This lets another worker resume polling without creating a second purchase. Handle terminal states explicitly. A failed or expired checkout must not be retried as though payment were still processing. Create a new checkout only after the user or approved billing workflow requests it. When `paid` appears, release only the workload tied to that checkout. Keep unrelated queues behind their own credit and approval checks. This prevents one top-up from silently authorizing broader activity. ## Headers Your API key. Session cookie authentication is also supported. ## Query parameters Checkout session ID for the top-up checkout. ## Response ### 200 Paid Payment succeeded. Credits have been added to the account. Always `"paid"`. Dollar amount requested for the top-up. Credit amount granted as a Bigint string. ```json theme={null} { "amount_dollars": 25, "credits": "166666", "status": "paid" } ``` ### 200 Processing Payment has not reached a final state yet. Poll again later. Always `"processing"`. Dollar amount requested for the top-up, when available. Pending credit amount as a Bigint string, when available. ```json theme={null} { "amount_dollars": 25, "credits": "166666", "status": "processing" } ``` ### 200 Failed Payment failed. Create a new top-up checkout before retrying payment. Always `"failed"`. ```json theme={null} { "status": "failed" } ``` ### 200 Expired The checkout session expired before payment completed. Always `"expired"`. ```json theme={null} { "status": "expired" } ``` ### 400 Invalid input ```json theme={null} { "error": "invalid_input" } ``` The `session_id` query parameter is missing. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 404 Not found ```json theme={null} { "error": "not_found" } ``` No top-up checkout exists for that session ID on this account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [Top Up Credits](/api-reference/credits/topup) · [Quick Top-Up](/api-reference/credits/quick-topup) · [Get Credits](/api-reference/credits/get) · [Billing Guide](/guides/billing) # Create a Tweet Draft for Review with the Xquik API Source: https://docs.xquik.com/api-reference/drafts/create POST /drafts Save one Xquik tweet draft with text, optional topic and goal, 25,000-character validation, API authentication, and clear 400, 401 or 429 recovery steps. ```json theme={null} { "id": "42", "text": "AI is the future of productivity", "topic": "AI trends", "goal": "engagement", "createdAt": "2025-01-15T12:00:00Z", "updatedAt": "2025-01-16T09:30:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Save a Tweet Draft Before Publishing Create one private tweet draft inside your authenticated Xquik account. Supply the proposed text and optional composition context. The response returns a stable Xquik draft ID for review or deletion. This request does not publish a tweet. It sends nothing to followers and creates no replies, reposts, likes, impressions, or public tweet ID. Xquik drafts are separate from [X's native Unsent posts](https://help.x.com/en/using-x/how-to-post). Creating a record here does not add anything to the X compose interface. **Free** - does not consume credits ```bash cURL theme={null} curl --fail-with-body \ --request POST https://xquik.com/api/v1/drafts \ --header "x-api-key: xq_YOUR_KEY_HERE" \ --header "Content-Type: application/json" \ --data '{ "text": "Just shipped dark mode. What feature should we build next?", "topic": "product update", "goal": "conversation" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/drafts", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ text: "Just shipped dark mode. What feature should we build next?", topic: "product update", goal: "conversation", }), }); const draft = await response.json(); if (!response.ok) { throw new Error(`${response.status} ${draft.error}: ${draft.message}`); } console.log(draft.id, draft.text, draft.createdAt); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/drafts", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "text": "Just shipped dark mode. What feature should we build next?", "topic": "product update", "goal": "conversation", }, ) draft = response.json() if response.status_code != 201: raise RuntimeError( f'{response.status_code} {draft["error"]}: {draft["message"]}' ) print(draft["id"], draft["text"], draft["createdAt"]) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, err := json.Marshal(map[string]interface{}{ "text": "Just shipped dark mode. What feature should we build next?", "topic": "product update", "goal": "conversation", }) if err != nil { panic(err) } req, err := http.NewRequest("POST", "https://xquik.com/api/v1/drafts", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var draft map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&draft); err != nil { panic(err) } if resp.StatusCode != http.StatusCreated { panic(fmt.Sprintf("%d %v: %v", resp.StatusCode, draft["error"], draft["message"])) } fmt.Println(draft["id"], draft["text"], draft["createdAt"]) } ``` ## Choose the Tweet Draft Fields Send a JSON object with one required field and two optional fields. | Field | Requirement | Draft use | | ------- | -------------------------------------------- | ---------------------------------------- | | `text` | Required string with 1 to 25,000 characters | Store the exact proposed tweet text. | | `topic` | Optional string, stored up to 500 characters | Record the intended subject. | | `goal` | Optional supported enum value | Record the intended composition outcome. | The supported `goal` values are `engagement`, `followers`, `authority`, and `conversation`. Match the lowercase value exactly. An unsupported `goal` is silently omitted. A non-string goal is also omitted. Validate this field before sending the request. A topic longer than 500 characters is silently truncated. A non-string topic is omitted. Trim and validate the topic inside your client. The `text` field behaves differently. Missing, empty, non-string, or oversized text returns `400 invalid_input`. Xquik never silently truncates draft text. The 25,000-character storage limit is not a publishing guarantee. Confirm the connected account's X posting rules before a separate write request. ## Read the Created Draft A successful request returns `201 Created` and one canonical draft object. | Field | Meaning | Next action | | ----------- | --------------------------- | ------------------------------------------- | | `id` | Stable Xquik draft ID | Store it for retrieval or deletion. | | `text` | Exact accepted text | Compare it with the submitted copy. | | `topic` | Accepted optional topic | Confirm any truncation was acceptable. | | `goal` | Accepted optional goal | Confirm the requested value was supported. | | `createdAt` | ISO 8601 creation timestamp | Record when review began. | | `updatedAt` | ISO 8601 update timestamp | Compare later retrieval with this response. | Optional `topic` and `goal` fields are omitted when unset or invalid. Do not expect those keys to contain `null`. The response contains no thread order, media attachment, reply target, schedule, publishing status, or public tweet ID. ## Build a Tweet Draft Review Workflow Draft creation supports a human or agent approval checkpoint. 1. Prepare one exact tweet text string. 2. Add a short topic when reviewers need context. 3. Choose one supported composition goal. 4. Create the Xquik draft once. 5. Store the returned draft ID. 6. Retrieve that ID before final approval. 7. Compare text, context, and timestamps. 8. Publish approved text through a separate X write route. 9. Delete the saved draft after retention requirements allow it. This route provides no edit operation. Create a replacement draft when text must change. Keep the old ID until reviewers approve the replacement. This route also provides no idempotency key. Repeating the same successful request creates another draft record. Persist every `201` before creating again. After a lost response, list and reconcile drafts before retrying. Use [List Drafts](/api-reference/drafts/list) to reconcile duplicate records. Use [Get Draft](/api-reference/drafts/get) before publishing or deleting one. ## Keep Xquik and Native X Drafts Separate Native X drafts appear under Unsent posts in the X compose interface. Native thread drafts can contain several connected posts. Native drafts can also carry photos, GIFs, or video. An Xquik draft stores one text string plus optional composition context. It does not mirror native draft features or publish automatically. | Draft capability | Xquik Create Draft | Native X draft | | ------------------------------------ | ------------------ | ------------------------------- | | Store one text string through an API | Yes | No through this endpoint | | Store optional topic and goal | Yes | Not represented here | | Appear under X Unsent posts | No | Yes | | Preserve a native thread sequence | No | Supported by native X workflows | | Preserve native media attachments | No | Supported by native X workflows | | Publish during draft creation | No | No | Keep thread items, media IDs, reply targets, and scheduling instructions in your publishing workflow. Do not assume the draft response contains them. ## Recover From Draft Creation Errors | Status | Error | Cause | Fix | | ------ | --------------------- | -------------------------------------------------- | --------------------------------------------- | | `201` | Draft object | Xquik stored the draft | Save the returned ID. | | `400` | `invalid_input` | `text` is missing, empty, non-string, or oversized | Send 1 to 25,000 characters. | | `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. | | `429` | `rate_limit_exceeded` | Too many requests reached the route | Wait for `Retry-After`, then retry carefully. | Never retry `400` without changing the body. Never retry `401` without replacing the credential. After `429`, preserve the submitted text. Retry only after the server's delay. Reconcile the list when the original request may have succeeded. ## Answer Tweet Draft Creation Questions ### How Can I Save a Tweet Draft Through an API? Send `POST /drafts` with a JSON `text` string. Authenticate with an Xquik API key or OAuth bearer token. ### Does Creating a Draft Publish the Tweet? No. The request stores private Xquik text only. Publish through a separate write route after approval. ### Does the Draft Appear in X Unsent Posts? No. Xquik draft records and native X drafts are separate collections. ### Can One Draft Store a Twitter Thread? No. One draft stores one text string. Create and preserve thread structure in your publishing workflow. ### Can I Attach Media or a Reply Target? No. The canonical draft fields contain no media or reply target. Add those details during a separate publishing request. ### Can I Update a Saved Tweet Draft? No update route exists. Create a replacement, approve it, then delete the old record when safe. ### Does Creating a Tweet Draft Consume Credits? No. This authenticated draft creation request is free. ## Headers Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). An OAuth bearer token formatted as `Bearer YOUR_TOKEN`. Send this header or `x-api-key`, not both. Must be `application/json`. ## Body Exact draft text. Send 1 to 25,000 characters. Oversized text returns `400`. Optional composition topic. Values longer than 500 characters are silently truncated. Optional goal: `engagement`, `followers`, `authority`, or `conversation`. Other values are silently omitted. ## Response ### 201 Created Unique Xquik draft ID. Exact accepted tweet text. Optional accepted topic. Omitted when unset. Optional accepted goal. Omitted when unset. ISO 8601 creation timestamp. ISO 8601 update timestamp. ```json theme={null} { "id": "42", "text": "Just shipped dark mode. What feature should we build next?", "topic": "product update", "goal": "conversation", "createdAt": "2026-02-24T10:30:00.000Z", "updatedAt": "2026-02-24T10:30:00.000Z" } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` The `text` field is missing, empty, non-string, or longer than 25,000 characters. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Authentication is missing or invalid. Replace the credential before retrying. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests reached the route. Wait for `Retry-After` before retrying. **Next steps:** [List Drafts](/api-reference/drafts/list) to inventory saved text, [Get Draft](/api-reference/drafts/get) to retrieve one record, or [Delete Draft](/api-reference/drafts/delete) to remove it. # Delete Tweet Drafts Safely with the Xquik API Source: https://docs.xquik.com/api-reference/drafts/delete DELETE /drafts/{id} Delete one Xquik tweet draft by ID without deleting a published post or native X draft. Handle empty 204 responses and 400, 401, 404 & 429 API errors safely. ```text theme={null} No response body. ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Delete One Saved Tweet Draft Delete one tweet draft from your Xquik account. The request removes its saved text, optional topic, optional goal, and timestamps. It does not publish the draft or call an X write route. This endpoint manages Xquik draft records only. It does not manage drafts under [X's native Unsent posts](https://help.x.com/en/using-x/how-to-post). It also does not delete published tweets, scheduled posts, or connected X accounts. Use the draft ID returned by [Create Draft](/api-reference/drafts/create) or [List Drafts](/api-reference/drafts/list). Fetch the draft before deletion when its text must receive human approval. Deletion is permanent. Xquik provides no restore endpoint for deleted tweet drafts. Confirm the saved text and draft ID before sending this request. **Free** - does not consume credits ```bash cURL theme={null} curl --include --request DELETE \ https://xquik.com/api/v1/drafts/42 \ --header "x-api-key: xq_YOUR_KEY_HERE" ``` ```javascript Node.js theme={null} const draftId = "42"; const response = await fetch(`https://xquik.com/api/v1/drafts/${draftId}`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); if (response.status !== 204) { const problem = await response.json(); throw new Error(`${response.status} ${problem.error}: ${problem.message}`); } console.log(`Deleted tweet draft ${draftId}`); ``` ```python Python theme={null} import requests draft_id = "42" response = requests.delete( f"https://xquik.com/api/v1/drafts/{draft_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) if response.status_code != 204: problem = response.json() raise RuntimeError( f'{response.status_code} {problem["error"]}: {problem["message"]}' ) print(f"Deleted tweet draft {draft_id}") ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { draftID := "42" req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/drafts/"+draftID, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { var problem struct { Error string `json:"error"` Message string `json:"message"` } if err := json.NewDecoder(resp.Body).Decode(&problem); err != nil { panic(err) } panic(fmt.Sprintf("%d %s: %s", resp.StatusCode, problem.Error, problem.Message)) } fmt.Printf("Deleted tweet draft %s\n", draftID) } ``` ## Handle 204 No Content A successful tweet draft deletion returns `204 No Content`. The response has no JSON, text, or deletion object. Check the status before parsing the body. Calling `response.json()` after a successful deletion throws an end-of-input error. Parse JSON only for `400`, `401`, `404`, or `429` responses. The `204` status confirms that Xquik deleted the matching draft record. It does not return the deleted text. Fetch and store any needed review copy first. ## Confirm the Correct Tweet Draft Use a read-before-delete flow for dashboards, agents, and scheduled cleanup jobs. 1. Fetch the draft through `GET /drafts/{id}`. 2. Compare its `id`, `text`, `topic`, `goal`, and timestamps. 3. Ask for confirmation when a person owns the draft. 4. Send `DELETE /drafts/{id}` once. 5. Accept only `204` as a successful deletion. 6. Remove the draft from your local queue. 7. List drafts again when reconciliation matters. Do not guess draft IDs. Use an ID returned to the authenticated account. A numeric ID owned by another account returns `404` without exposing ownership. ## Understand What Deletion Changes | Resource | Result | Reason | | --------------------------- | ---------------------- | -------------------------------------------- | | Matching Xquik draft | Deleted permanently | This route targets one stored draft ID. | | Draft text, topic, and goal | Deleted with the draft | These fields belong to that record. | | Native X or Twitter draft | Unchanged | Xquik cannot edit the X compose box. | | Published tweet or thread | Unchanged | Use a separate X write route for live posts. | | Scheduled post | Unchanged | Draft deletion does not cancel a schedule. | | Connected X account | Unchanged | The request changes no account connection. | A saved Xquik tweet draft is not a published tweet. It has an Xquik draft ID, not a public tweet ID. Deleting one cannot remove replies, reposts, likes, or media attached to a published post. ## Recover From Draft Deletion Errors | Status | Error | Cause | Fix | | ------ | --------------------- | ------------------------------------------------- | -------------------------------------------- | | `204` | No body | Xquik deleted the draft | Stop. Do not parse JSON. | | `400` | `invalid_id` | The path value is not a parseable draft ID | Copy an ID from Create Draft or List Drafts. | | `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. | | `404` | `draft_not_found` | The draft is absent or belongs to another account | Verify the ID and authenticated account. | | `429` | `rate_limit_exceeded` | Too many requests reached the route | Wait for `Retry-After`, then retry once. | A second deletion of the same ID returns `404`. The first request already removed the record. Treat that result as already absent only when your workflow permits that interpretation. Never retry `400`, `401`, or `404` without changing the request. Retrying the same invalid input creates noise and cannot restore a deleted draft. ## Answer Tweet Draft Deletion Questions ### Does This Delete a Published Tweet? No. This route deletes one saved Xquik draft. Use the documented tweet deletion route for a live post owned by the connected account. ### Does This Delete My Native X Draft? No. Native X drafts appear under Unsent posts. Xquik draft IDs identify separate records stored for API composition workflows. ### Can I Restore a Deleted Tweet Draft? No restore route exists. Save an approved copy before deletion when retention rules require one. ### Can I Delete Every Draft in One Request? No bulk deletion route exists. List drafts, confirm each target, and delete each ID separately. Apply your own concurrency and rate-limit controls. ### Why Does a Successful Delete Return No JSON? HTTP `204` means the deletion succeeded without response content. Check the status code instead of parsing a body. ### Why Does a Retry Return 404? The first successful request removed the draft. Later requests cannot find the same account-owned record. ### Does Deleting a Draft Consume Credits? No. This authenticated route is free and consumes no Xquik credits. ## Path Parameters The unique Xquik draft ID. Copy it from [Create Draft](/api-reference/drafts/create) or [List Drafts](/api-reference/drafts/list). ## Headers Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). An OAuth bearer token formatted as `Bearer YOUR_TOKEN`. Send this header or `x-api-key`, not both. ## Response The draft was deleted. Do not parse a response body. ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided draft ID is not a valid format. ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Missing or invalid API key. ```json theme={null} { "error": "draft_not_found", "message": "Draft not found." } ``` No draft exists with this ID, or it belongs to a different account. ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [List Drafts](/api-reference/drafts/list) to verify the draft was removed, or [Create Draft](/api-reference/drafts/create) to save a new one. # Get a Tweet Draft by ID with the Xquik API Source: https://docs.xquik.com/api-reference/drafts/get GET /drafts/{id} Retrieve one Xquik tweet draft by ID with saved text, optional topic, optional goal, creation time & update time. Handle 400, 401, 404 & 429 API errors. ```json theme={null} { "id": "42", "text": "AI is the future of productivity", "topic": "AI trends", "goal": "engagement", "createdAt": "2025-01-15T12:00:00Z", "updatedAt": "2025-01-16T09:30:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Retrieve One Tweet Draft by ID Retrieve one saved tweet draft from your Xquik account. The response contains its ID, text, optional topic, optional goal, and timestamps. It does not return thread order, media attachments, reply targets, or publishing results. Use the ID returned by [Create Draft](/api-reference/drafts/create) or [List Drafts](/api-reference/drafts/list). This route reads that record without creating, changing, publishing, or deleting it. Xquik drafts are separate from [X's native Unsent posts](https://help.x.com/en/using-x/how-to-post). This endpoint cannot retrieve drafts stored inside the X compose interface. **Free** - does not consume credits ```bash cURL theme={null} curl --fail-with-body \ https://xquik.com/api/v1/drafts/42 \ --header "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const draftId = "42"; const response = await fetch(`https://xquik.com/api/v1/drafts/${draftId}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const draft = await response.json(); if (!response.ok) { throw new Error(`${response.status} ${draft.error}: ${draft.message}`); } console.log(draft.id, draft.text, draft.updatedAt); ``` ```python Python theme={null} import requests draft_id = "42" response = requests.get( f"https://xquik.com/api/v1/drafts/{draft_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) draft = response.json() if response.status_code != 200: raise RuntimeError( f'{response.status_code} {draft["error"]}: {draft["message"]}' ) print(draft["id"], draft["text"], draft["updatedAt"]) ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { draftID := "42" req, err := http.NewRequest("GET", "https://xquik.com/api/v1/drafts/"+draftID, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var draft map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&draft); err != nil { panic(err) } if resp.StatusCode != http.StatusOK { panic(fmt.Sprintf("%d %v: %v", resp.StatusCode, draft["error"], draft["message"])) } fmt.Println(draft["id"], draft["text"], draft["updatedAt"]) } ``` ## Read the Tweet Draft Fields The API returns one compact draft object. Optional fields are omitted when they were not supplied during creation. | Field | Meaning | Review use | | ----------- | --------------------------- | --------------------------------------------------------------- | | `id` | Stable Xquik draft ID | Keep it for later retrieval or deletion. | | `text` | Exact saved tweet text | Review this copy before publishing. | | `topic` | Optional composition topic | Preserve the intended subject. | | `goal` | Optional composition goal | Check engagement, followers, authority, or conversation intent. | | `createdAt` | ISO 8601 creation timestamp | Record when the draft entered the workflow. | | `updatedAt` | ISO 8601 update timestamp | Compare the returned record with a cached review copy. | The `text` field contains one saved string. It can contain up to 25,000 characters. The `topic` field can contain up to 500 characters. The `goal` value can be `engagement`, `followers`, `authority`, or `conversation`. This response contains no public tweet ID. A draft remains private until a separate X write request publishes approved text. ## Keep Xquik and Native X Drafts Separate Native X drafts appear inside the X compose interface. Native thread drafts can contain several connected posts. Native posts can also include photos, GIFs, or video. An Xquik draft stores one text value plus optional composition context. It has no thread sequence, media collection, reply target, or native X draft ID. | Draft source | Retrieve it here? | Retrieval method | | ------------------------- | ----------------- | ------------------------------------ | | Xquik Create Draft API | Yes | Call `GET /drafts/{id}`. | | Xquik List Drafts API | Yes | Copy an ID, then call this route. | | X Unsent posts | No | Open the native X compose interface. | | Published tweet or thread | No | Use an X tweet read endpoint. | Do not send a public tweet ID to this route. Tweet IDs and Xquik draft IDs identify different resources. ## Build a Tweet Draft Review Workflow 1. Save tweet text through `POST /drafts`. 2. Store the returned Xquik draft ID. 3. Retrieve the draft before human or agent review. 4. Compare its text, topic, goal, and timestamps. 5. Approve or reject the exact returned text. 6. Publish approved text through a separate X write route. 7. Delete the saved draft when retention rules allow it. This route provides no edit operation. Create a new draft when approved text must change. Keep the old ID until reviewers accept the replacement. Use [Create Tweet](/api-reference/x-write/create-tweet) only after approval. Reading a draft never sends text to followers or creates likes and replies. ## Recover From Tweet Draft Lookup Errors | Status | Error | Cause | Fix | | ------ | --------------------- | ------------------------------------------------- | -------------------------------------------- | | `200` | Draft object | The account owns the requested draft | Read the returned fields. | | `400` | `invalid_id` | The path value is not a parseable draft ID | Copy an ID from Create Draft or List Drafts. | | `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. | | `404` | `draft_not_found` | The draft is absent or belongs to another account | Verify the ID and authenticated account. | | `429` | `rate_limit_exceeded` | Too many requests reached the route | Wait for `Retry-After`, then retry once. | The route returns the same `404` for missing drafts and other-account IDs. This boundary prevents clients from discovering another account's draft IDs. Never retry `400`, `401`, or `404` without changing the request. Retry `429` only after the server's delay. ## Answer Tweet Draft Retrieval Questions ### Where Can I Find an Xquik Tweet Draft? Call List Drafts first. Copy the returned `id`, then request this endpoint. ### Can This API Retrieve My Native X Drafts? No. Native X drafts remain under Unsent posts. This route reads Xquik records created through the draft API. ### Does One Draft Include a Twitter Thread? No. The response contains one text string. It includes no thread order or connected-post collection. ### Does the Response Include Media or Reply Targets? No. The canonical draft object contains no media, reply target, or tweet ID. ### Can This Route Publish or Update the Draft? No. This route only reads the saved record. Create replacement text or publish through separate endpoints. ### Can I Retrieve a Deleted Draft? No. A deleted draft returns `404`. Xquik provides no restore endpoint. ### Does Retrieving a Draft Consume Credits? No. This authenticated lookup is free and consumes no Xquik credits. ## Path Parameters The unique Xquik draft ID. Copy it from [Create Draft](/api-reference/drafts/create) or [List Drafts](/api-reference/drafts/list). ## Headers Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). An OAuth bearer token formatted as `Bearer YOUR_TOKEN`. Send this header or `x-api-key`, not both. ## Response ### 200 OK Unique draft ID. The draft tweet text. Topic the tweet is about. Omitted if not set. Optimization goal. Omitted if not set. ISO 8601 creation timestamp. ISO 8601 last update timestamp. ```json theme={null} { "id": "42", "text": "Just shipped dark mode. What feature should we build next?", "topic": "product update", "goal": "conversation", "createdAt": "2026-02-24T10:30:00.000Z", "updatedAt": "2026-02-24T10:30:00.000Z" } ``` ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided draft ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "draft_not_found", "message": "Draft not found." } ``` No draft exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [List Drafts](/api-reference/drafts/list) to see all your drafts, [Create Draft](/api-reference/drafts/create) to save a new one, or [Delete Draft](/api-reference/drafts/delete) to remove this draft. # List Xquik Tweet Drafts with Cursor-Based Pagination Source: https://docs.xquik.com/api-reference/drafts/list GET /drafts List Xquik tweet drafts with exact text, optional topic and goal, timestamps, stable cursor pagination, authentication, and clear 401 or 429 recovery. ```json theme={null} { "drafts": [], "hasMore": false } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## List Saved Tweet Drafts List tweet drafts stored in your authenticated Xquik account. Each result contains saved text, an optional topic, an optional goal, and timestamps. Use this route to build a draft inventory or review queue. It returns drafts newest first and supports cursor pagination. The route never publishes draft text or sends it to followers. Xquik drafts are separate from [X's native Unsent posts](https://help.x.com/en/using-x/how-to-post). This API cannot list drafts saved inside the X compose interface. **Free** - does not consume credits ```bash cURL theme={null} curl --fail-with-body \ "https://xquik.com/api/v1/drafts?limit=20" \ --header "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/drafts?limit=20", { headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const page = await response.json(); if (!response.ok) { throw new Error(`${response.status} ${page.error}: ${page.message}`); } for (const draft of page.drafts) { console.log(draft.id, draft.text, draft.createdAt); } ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/drafts", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"limit": 20}, ) page = response.json() if response.status_code != 200: raise RuntimeError( f'{response.status_code} {page["error"]}: {page["message"]}' ) for draft in page["drafts"]: print(draft["id"], draft["text"], draft["createdAt"]) ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/drafts?limit=20", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var page map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&page); err != nil { panic(err) } if resp.StatusCode != http.StatusOK { panic(fmt.Sprintf("%d %v: %v", resp.StatusCode, page["error"], page["message"])) } fmt.Println(page["drafts"], page["hasMore"]) } ``` ## Understand the Tweet Draft List The response contains one `drafts` array and pagination fields. An empty array is a successful result. It means the account currently has no saved drafts. Drafts use descending creation order. A newer `createdAt` value appears first. The draft ID breaks ties between equal creation times. This order stays predictable while clients follow returned cursors. The list contains Xquik draft records only. It excludes published tweets, scheduled posts, native X drafts, and drafts owned by another account. Each draft contains one saved text string. The canonical draft object contains no thread order, media attachment, reply target, or public tweet ID. ## Paginate Through Every Draft Request up to 50 drafts per page. The default and maximum `limit` are both 50. A smaller value helps clients process short review batches. Follow this cursor workflow: 1. Request the first page without `afterCursor`. 2. Process every draft in the returned order. 3. Check `hasMore` after processing the page. 4. Copy `nextCursor` when `hasMore` is `true`. 5. Send that value as the next `afterCursor`. 6. Stop when `hasMore` becomes `false`. Treat `nextCursor` as an opaque value. Never decode, edit, or construct it. Keep only the latest cursor after a page succeeds. The final page omits `nextCursor`. Do not expect an empty string or `null`. Use `hasMore` as the loop condition. This route exposes no topic, goal, text, date, or status filter. Filter the returned draft objects inside your client when a smaller review set is needed. Do not paginate with timestamps or guessed draft IDs. Those values cannot replace the returned cursor. A cursor from another account also cannot select that account's drafts. ## Read the Draft Inventory Fields Optional fields are omitted when they were not supplied during draft creation. Check for `topic` and `goal` before reading them. | Field | Meaning | Inventory use | | -------------------- | ------------------------------- | ---------------------------------------------------------------- | | `drafts[].id` | Stable Xquik draft ID | Retrieve or delete the exact draft. | | `drafts[].text` | Exact saved tweet text | Review the proposed post copy. | | `drafts[].topic` | Optional composition topic | Group drafts by intended subject. | | `drafts[].goal` | Optional composition goal | Review engagement, followers, authority, or conversation intent. | | `drafts[].createdAt` | ISO 8601 creation timestamp | Sort or record intake time. | | `drafts[].updatedAt` | ISO 8601 update timestamp | Compare a result with a cached copy. | | `hasMore` | Whether another page exists | Continue or stop pagination. | | `nextCursor` | Opaque cursor for the next page | Pass it through unchanged. | The `text` field can contain up to 25,000 characters. The optional `topic` field can contain up to 500 characters. A `goal` can be `engagement`, `followers`, `authority`, or `conversation`. ## Build a Tweet Draft Review Queue Use list pagination before retrieval, publishing, or cleanup. 1. List the newest Xquik tweet drafts. 2. Store each returned draft ID once. 3. Review its text, topic, goal, and timestamps. 4. Retrieve one draft again before final approval. 5. Publish approved text through a separate X write route. 6. Delete obsolete drafts after required review. 7. Continue until `hasMore` is `false`. Listing a draft does not reserve, approve, publish, or delete it. Your client must record those workflow states separately. The list response provides no draft count across every page. Count processed IDs locally when an inventory total matters. Deduplicate by `id` during long runs where drafts can be created or deleted concurrently. Use [Get Draft](/api-reference/drafts/get) before destructive cleanup. Use [Create Tweet](/api-reference/x-write/create-tweet) only after content approval. ## Keep Native X Drafts Separate Native X drafts appear under Unsent posts inside the compose interface. Native thread drafts may contain several connected posts. Native drafts may also contain photos, GIFs, or video. An Xquik draft stores one text value and optional composition context. It does not mirror the native X draft collection. | Draft source | Listed here? | Correct action | | ------------------------- | ------------ | ------------------------------------- | | Xquik Create Draft API | Yes | Paginate this endpoint. | | Xquik Get Draft API | Yes | Match the same Xquik draft ID. | | X Unsent posts | No | Open the native X compose interface. | | Published tweet or thread | No | Use an X tweet read endpoint. | | Scheduled post | No | Use the matching scheduling workflow. | Do not send a public tweet ID as `afterCursor`. Public tweet IDs and draft cursors identify different resources. ## Recover From Draft List Errors | Status | Error | Cause | Fix | | ------ | --------------------- | ------------------------------------ | ----------------------------------------- | | `200` | Draft page | The authenticated request succeeded | Process `drafts`, then inspect `hasMore`. | | `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. | | `429` | `rate_limit_exceeded` | Too many requests reached the route | Wait for `Retry-After`, then retry once. | An empty `drafts` array is not a `404`. It is a valid `200` response. Never retry `401` without replacing the credential. Retry `429` only after the server's delay. Preserve the last successful cursor before retrying a page. ## Answer Tweet Draft List Questions ### How Can I List My Saved Tweet Drafts? Call `GET /drafts` with an API key or OAuth bearer token. The response lists drafts owned by that authenticated Xquik account. ### Does This API List Native Twitter or X Drafts? No. Native drafts remain under Unsent posts. This route lists records created through the Xquik draft API. ### How Many Tweet Drafts Can One Page Return? One page returns at most 50 drafts. Use `nextCursor` when `hasMore` is `true`. ### Can I Search Draft Text or Filter by Topic? No server-side search or filter parameter exists. Paginate, then filter fields inside your client. ### Does One Draft Include a Thread or Media? No. One result contains a text string and optional composition context. It contains no thread sequence, media collection, or reply target. ### Are Deleted Drafts Included? No. Deleted drafts are absent. Xquik provides no deleted-draft restore route. ### Does Listing Tweet Drafts Consume Credits? No. This authenticated list request is free and consumes no Xquik credits. ## Query Parameters Results per page. Default `50`, maximum `50`. Opaque cursor from `nextCursor`. Pass it unchanged to fetch the next page. ## Headers Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). An OAuth bearer token formatted as `Bearer YOUR_TOKEN`. Send this header or `x-api-key`, not both. ## Response ### 200 OK Draft objects ordered by creation time, newest first. Unique Xquik draft ID. Exact saved tweet text. Optional draft topic. Omitted when unset. Optional composition goal. Omitted when unset. ISO 8601 creation timestamp. ISO 8601 update timestamp. Whether another page exists. Opaque next-page cursor. Present only when `hasMore` is `true`. ```json theme={null} { "drafts": [ { "id": "42", "text": "Just shipped dark mode. What feature should we build next?", "topic": "product update", "goal": "conversation", "createdAt": "2026-02-24T10:30:00.000Z", "updatedAt": "2026-02-24T10:30:00.000Z" }, { "id": "38", "text": "Three lessons from building a real-time API.", "goal": "authority", "createdAt": "2026-02-23T16:15:00.000Z", "updatedAt": "2026-02-23T16:15:00.000Z" } ], "hasMore": true, "nextCursor": "MjAyNi0wMi0yM1QxNjoxNTowMC4wMDBafDM4" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Authentication is missing or invalid. Replace the credential before retrying. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests reached the route. Wait for `Retry-After` before retrying. **Related:** [Create Draft](/api-reference/drafts/create) to save tweet text, [Get Draft](/api-reference/drafts/get) to retrieve one record, or [Delete Draft](/api-reference/drafts/delete) to remove it. # Twitter Giveaway Picker API & Random Winner Draw Source: https://docs.xquik.com/api-reference/draws/create POST /draws Select random giveaway winners from a tweet's replies, reposts, likes, quotes, or followers with eligibility and exclusion rules. See response fields. ```json theme={null} { "id": "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "tweetId": "1234567890", "totalEntries": 250, "validEntries": 200, "winners": [] } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits" } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
Use this Twitter giveaway picker API to select primary and backup winners. Filter replies by reposts, follows, keywords, hashtags, mentions, or language. Add account-age, follower-count, or unique-author rules. **Metered draw execution** · source lookup, replies, optional retweeters, and optional follow checks consume credits Remaining credits cap how many replies and retweeters Xquik can inspect before filters run. `totalEntries` and `validEntries` describe that inspected candidate set, not necessarily every reply on the source tweet. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/draws \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "tweetUrl": "https://x.com/xquik/status/1893456789012345678", "winnerCount": 3, "backupCount": 2, "mustRetweet": true, "filterMinFollowers": 10, "requiredKeywords": ["giveaway"] }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/draws", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ tweetUrl: "https://x.com/xquik/status/1893456789012345678", winnerCount: 3, backupCount: 2, mustRetweet: true, filterMinFollowers: 10, requiredKeywords: ["giveaway"], }), }); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/draws", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "tweetUrl": "https://x.com/xquik/status/1893456789012345678", "winnerCount": 3, "backupCount": 2, "mustRetweet": True, "filterMinFollowers": 10, "requiredKeywords": ["giveaway"], }, ) data = response.json() print(data) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" ) func main() { payload := map[string]interface{}{ "tweetUrl": "https://x.com/xquik/status/1893456789012345678", "winnerCount": 3, "backupCount": 2, "mustRetweet": true, "filterMinFollowers": 10, "requiredKeywords": []string{"giveaway"}, } body, err := json.Marshal(payload) if err != nil { log.Fatal(err) } req, err := http.NewRequest("POST", "https://xquik.com/api/v1/draws", bytes.NewReader(body)) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(respBody)) } ``` | Giveaway draw column | Request or response source | Audit rule | | -------------------- | -------------------------- | ------------------------------------------ | | Draw ID | Response `id` | Use this public ID for review and export. | | Source tweet | Response `tweetId` | Preserve the giveaway tweet identifier. | | Inspected entries | Response `totalEntries` | Record the credit-bounded candidate count. | | Eligible entries | Response `validEntries` | Compare this count with every filter. | | Winner order | `winners[].position` | Preserve the original selection order. | | Winner username | `winners[].authorUsername` | Store the selected X account. | | Winning reply | `winners[].tweetId` | Retain the qualifying reply identifier. | | Backup state | `winners[].isBackup` | Separate primary and backup winners. | ## Headers Your API key. Session cookie authentication is also supported. Must be `application/json`. ## Body Full tweet URL to run the draw on. Accepts `x.com` and `twitter.com` formats (e.g. `https://x.com/user/status/1893456789012345678`). Number of winners to draw. Defaults to `1` if omitted. Number of backup winners to draw. Backup winners are selected in case primary winners are disqualified. When `true`, each author can only win once regardless of how many replies they posted. When `true`, only entries from users who retweeted the original tweet are eligible. X username that entrants must follow to be eligible. The `@` prefix is stripped if included. Minimum follower count required for eligible entries. Minimum account age in days. Accounts younger than this are excluded. Filter entries by tweet language code (e.g. `en`, `tr`, `es`). Array of keywords that must appear in the reply text. Entries missing any keyword are excluded. Array of hashtags that must appear in the reply text. Include the `#` prefix. Array of usernames that must be mentioned in the reply text. Include the `@` prefix. ## Response ### 201 Created Draw public ID returned by Xquik. X tweet ID extracted from the URL. Candidate entries inspected for this draw after the credit-derived cap. This may be lower than the source tweet's full reply count. Entries from the inspected candidate set that passed all filters. Selected winners and backup winners. **Winner object fields:** Winner position (1-indexed). X username of the winner. Tweet ID of the winning reply. `true` if this is a backup winner. ```json theme={null} { "id": "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "tweetId": "1893456789012345678", "totalEntries": 847, "validEntries": 312, "winners": [ { "position": 1, "authorUsername": "alice_web3", "tweetId": "1893456789012345700", "isBackup": false }, { "position": 2, "authorUsername": "bob_dev", "tweetId": "1893456789012345701", "isBackup": false }, { "position": 3, "authorUsername": "charlie_nft", "tweetId": "1893456789012345702", "isBackup": false }, { "position": 4, "authorUsername": "diana_crypto", "tweetId": "1893456789012345703", "isBackup": true }, { "position": 5, "authorUsername": "eve_trades", "tweetId": "1893456789012345704", "isBackup": true } ] } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Missing or malformed request body" } ``` Missing or malformed request body. Ensure `tweetUrl` is a string. ### 400 Invalid Tweet URL ```json theme={null} { "error": "invalid_tweet_url", "message": "Invalid tweet URL format" } ``` The `tweetUrl` could not be parsed. Must be a valid `x.com` or `twitter.com` status URL. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key / session cookie. ### 402 Insufficient credits ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` The available balance cannot cover the minimum draw cost. A later final deduction failure also returns `insufficient_credits`. No draw result is persisted after that failure. Check your [credit balance](/api-reference/account/get). ### 404 Not Found ```json theme={null} { "error": "tweet_not_found", "message": "Tweet not found" } ``` The target tweet does not exist, was deleted, or the ID is invalid. ### 424 X API Dependency Failed ```json theme={null} { "error": { "type": "dependency_error", "code": "x_api_unavailable", "message": "X data source temporarily unavailable" } } ``` Send `xquik-api-contract: 2026-04-29` to receive this status for dependency failures that return `502` by default. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ### 502 X API Unavailable ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable" } ``` The read service returned an error. Retry after a short delay. **Next steps:** [Get Draw](/api-reference/draws/get) to retrieve full draw details including tweet metadata, [Export Draw](/api-reference/draws/export) to download results as CSV/XLSX/Markdown, or [List Draws](/api-reference/draws/twitter-giveaway-history) to see your draw history. # Twitter Giveaway CSV Export API & Winner Lists Source: https://docs.xquik.com/api-reference/draws/export GET /draws/{id}/export Export selected Twitter giveaway winners or inspected reply entries as CSV, XLSX, JSON, Markdown, PDF, or text. Preserve columns, order, and filenames. ```text theme={null} ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
Download a Twitter giveaway winner list or inspected reply entries. Choose CSV, XLSX, JSON, Markdown, PDF, or plain text. Preserve the returned filename and exact row order for every handoff. Use `type=winners` for selected primary and backup winners. Use `type=entries` for every stored reply inspected during the draw. Entry exports include passing and failing replies. ## Export Twitter Giveaway Winners or Entries Choose the export type before choosing its file format. The two export types contain different columns and answer different review questions. **Free** - does not consume credits Set `type=winners`. Export ordered primary and backup winners with their selected reply text. Set `type=entries`. Export stored replies with filter results and detected languages. Choose CSV or XLSX for sorting, filtering, sponsor review, and fulfillment. Choose JSON, Markdown, PDF, or text for scripts and review records. The export does not include source tweet metadata or create-time filter rules. Join the file with [Get Draw](/api-reference/draws/get) using the draw ID. Preserve the original create request when reviewers need eligibility rules. ## Download a Giveaway Winner CSV ```bash cURL theme={null} curl --fail-with-body \ "https://xquik.com/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345/export?format=csv&type=winners" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ --output twitter-giveaway-winners.csv ``` ```javascript Node.js theme={null} import { writeFile } from "node:fs/promises"; const drawId = "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345"; const response = await fetch( `https://xquik.com/api/v1/draws/${drawId}/export?format=csv&type=winners`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, } ); if (!response.ok) { const problem = await response.json(); throw new Error(problem.message || "Giveaway winner export failed."); } const bytes = Buffer.from(await response.arrayBuffer()); await writeFile("twitter-giveaway-winners.csv", bytes); process.stdout.write( `${response.headers.get("content-disposition") || "download saved"}\n`, ); ``` ```python Python theme={null} import requests draw_id = "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345" response = requests.get( f"https://xquik.com/api/v1/draws/{draw_id}/export", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"format": "csv", "type": "winners"}, timeout=30, ) response.raise_for_status() with open("twitter-giveaway-winners.csv", "wb") as export_file: export_file.write(response.content) ``` ```go Go theme={null} package main import ( "fmt" "io" "log" "net/http" "os" ) func main() { drawID := "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345" req, err := http.NewRequest("GET", "https://xquik.com/api/v1/draws/"+drawID+"/export?format=csv&type=winners", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, readErr := io.ReadAll(resp.Body) if readErr != nil { log.Fatal(readErr) } log.Fatalf("giveaway export failed with %d: %s", resp.StatusCode, string(body)) } file, err := os.Create("twitter-giveaway-winners.csv") if err != nil { log.Fatal(err) } defer file.Close() if _, err := io.Copy(file, resp.Body); err != nil { log.Fatal(err) } fmt.Println(resp.Header.Get("Content-Disposition")) } ``` Never parse a successful export as JSON. The `200` body contains file bytes. Parse JSON only after a non-success status. ## Choose a Twitter Giveaway Export Format Every format represents the same selected rows. Serialization and filename extensions change. Column selection depends only on `type`. Use `format=csv` for spreadsheets and imports. Formula-like reply text is neutralized before download. Use `format=xlsx` for a native workbook with a bold header row. Use `format=json` for scripts, databases, and structured giveaway archives. Use `format=md` for a compact table in repositories or review tickets. Use `format=md-document` for one titled section per winner or entry. Use `format=pdf` for a readable handoff. Entry PDFs include up to 10,000 rows. Use `format=txt` for line-oriented review without spreadsheet software. CSV, JSON, Markdown, text, and XLSX entry exports include up to 100,000 rows. PDF entry exports include up to 10,000 rows. Winner exports contain selected winners. ## Understand Winner Export Columns Winner rows are ordered by their 1-indexed draw position. Do not sort them by username before publication. `Position` preserves the original winner order. Keep it in every handoff. `Username` identifies the selected X account at draw time. `Text` contains the selected reply text stored for the draw. `Backup` is `true` for a backup winner. Keep backup rows separate. The winner export does not include the winning reply ID. Retrieve draw detail when verification needs each winner's `tweetId`. ## Understand Giveaway Entry Export Columns Entry rows preserve stored insertion order. They include passing and failing replies from the inspected candidate set. `Username` identifies the reply author stored during draw processing. `Text` contains the stored reply used during eligibility checks. `Passed Filter` reports whether that reply passed every configured filter. `Language` contains the stored language code. It can be empty. Do not call every exported entry eligible. Check `Passed Filter` first. The entry file cannot explain which individual rule rejected a reply. ## Build a Giveaway Audit Handoff Pair both exports with the draw snapshot. This keeps winner selection separate from inspected candidate evidence. ```json theme={null} { "draw_id": "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "draw_detail_path": "/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "winner_export_path": "/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345/export?format=csv&type=winners", "entry_export_path": "/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345/export?format=csv&type=entries", "create_request_stored": true } ``` Store the original create request separately. It contains hashtags, keywords, mentions, language, follower rules, and other eligibility settings. ## Path Parameters The public draw ID. Retrieve it from [Giveaway History](/api-reference/draws/twitter-giveaway-history) or draw creation. ## Headers Send your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. ## Query Parameters Choose `csv`, `json`, `md`, `md-document`, `pdf`, `txt`, or `xlsx`. Choose `winners` or `entries`. The default is `winners`. ## Response ### 200 File Download Returns a file download. The response includes a `Content-Disposition` header with the filename. `format=csv` returns `text/csv; charset=utf-8` with filenames like `draw-winners-*.csv`. `format=json` returns `application/json; charset=utf-8` with filenames like `draw-winners-*.json`. `format=md` returns `text/markdown; charset=utf-8` with filenames like `draw-winners-*.md`. `format=md-document` returns `text/markdown; charset=utf-8` with filenames like `draw-winners-*.md`. `format=pdf` returns `application/pdf` with filenames like `draw-winners-*.pdf`. `format=txt` returns `text/plain; charset=utf-8` with filenames like `draw-winners-*.txt`. `format=xlsx` returns `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` with filenames like `draw-winners-*.xlsx`. Entry exports use the same suffix pattern with `draw-entries-*` filenames. **Winner export columns:** Position, Username, Text, Backup **Entry export columns:** Username, Text, Passed Filter, Language Entry exports are capped at 100,000 rows (10,000 for PDF). ### 400 Invalid Parameters ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` The `format` value is missing or unsupported. The `type` value can also be invalid. Fix both values before retrying. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` The API key or OAuth bearer token is missing or invalid. Replace it first. ### 404 Draw Not Found ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` No accessible draw matches that ID. Check the ID and authenticated account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Honor `Retry-After`, then retry the identical export request. ## Handle Giveaway Export Responses Save the binary body. Preserve the `Content-Disposition` filename when your storage policy allows it. Supply one supported format. Use only `winners` or `entries` for type. Replace the API key or OAuth bearer token before retrying. Confirm the public draw ID and the account owning that draw. Wait for `Retry-After`. Reuse the same draw ID, format, and type. ## Twitter Giveaway Export Questions ### How Do I Download Twitter Giveaway Winners as CSV? Call this endpoint with `format=csv&type=winners`. Save the binary response as a CSV file. Check the HTTP status before opening it. ### Can I Export Every Inspected Giveaway Entry? Yes. Set `type=entries`. The file contains stored replies from the inspected candidate set. It does not fetch new replies. ### Does an Entry Export Contain Only Eligible Replies? No. It contains passing and failing stored replies. Use `Passed Filter` to separate them. ### Which Giveaway Export Opens in Excel? Choose `format=xlsx` for a native workbook. Choose CSV for Excel, Google Sheets, imports, or lightweight processing. ### How Do I Identify Backup Winners? Export winners and read `Backup`. A `true` value marks a backup winner. Preserve the original `Position` value. ### Can I Export More Than 100,000 Giveaway Entries? No single non-PDF entry export exceeds 100,000 stored rows. PDF exports include up to 10,000 rows. The endpoint has no cursor. ### Does the Export Include Giveaway Eligibility Rules? No. Preserve the original draw request separately. The entry export reports only the combined pass result. ### Does the Export Include Source Tweet Metrics? No. Call [Get Draw](/api-reference/draws/get) for source tweet metrics, candidate counts, status, and timestamps. ### What Happens When a Winner Export Has No Rows? The file can contain headers or an empty collection. Treat that as an empty result, not a transport failure. ### Can I Use the File for Sponsor Proof? Yes, but include draw detail and the stored create request. The export alone cannot reconstruct eligibility rules. ### Does a Giveaway Export Consume Credits? No. Exporting an existing draw is free. Creating a new draw can consume credits. **Related:** [Get Draw](/api-reference/draws/get) verifies winners. [Giveaway History](/api-reference/draws/twitter-giveaway-history) finds draw IDs. [Create Draw](/api-reference/draws/create) starts a new selection. # Twitter Giveaway Winner API & Draw Verification Source: https://docs.xquik.com/api-reference/draws/get GET /draws/{id} Retrieve one Twitter giveaway draw by ID. Verify source tweet metrics, entry counts, ordered primary and backup winners, timestamps, and result status. ```json theme={null} { "draw": { "id": "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "tweetUrl": "https://x.com/elonmusk/status/1234567890", "tweetId": "1234567890", "tweetText": "Giving away 3 Tesla Model 3s!", "tweetAuthorUsername": "elonmusk" }, "winners": [] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
Retrieve one Twitter giveaway result with its draw ID. Inspect the source tweet snapshot, candidate counts, timestamps, and ordered winner rows. Separate primary winners from backup winners before publishing results. This endpoint does not return create-time eligibility rules or exclusion reasons. Preserve the original `POST /draws` request when an audit needs those rules. ## Verify One Twitter Giveaway Draw Use this route to review one completed or pending draw. Keep its draw ID with winner announcements, support records, and exports. **Free** - does not consume credits Read the tweet ID, URL, text, author, and engagement counts captured for the draw. Compare `totalEntries` with `validEntries`. Neither value reports the number of winners. Preserve `position`, `authorUsername`, `tweetId`, and `isBackup` for every selected account. Store `createdAt` for creation. Store `drawnAt` when winner selection has finished. Use [Giveaway History](/api-reference/draws/twitter-giveaway-history) to find a draw ID. Use [Export Draw](/api-reference/draws/export) for winner or entry files. ```bash cURL theme={null} curl --fail-with-body https://xquik.com/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const drawId = "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345"; const response = await fetch(`https://xquik.com/api/v1/draws/${drawId}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const result = await response.json(); if (!response.ok) { throw new Error(result.message || "Giveaway draw request failed."); } const verification = { draw_id: result.draw.id, tweet_id: result.draw.tweetId, tweet_url: result.draw.tweetUrl, status: result.draw.status, total_entries: result.draw.totalEntries, valid_entries: result.draw.validEntries, created_at: result.draw.createdAt, drawn_at: result.draw.drawnAt || null, primary_winners: result.winners.filter((winner) => !winner.isBackup), backup_winners: result.winners.filter((winner) => winner.isBackup), }; process.stdout.write(`${JSON.stringify(verification)}\n`); ``` ```python Python theme={null} import requests draw_id = "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345" response = requests.get( f"https://xquik.com/api/v1/draws/{draw_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) response.raise_for_status() result = response.json() verification = { "draw_id": result["draw"]["id"], "tweet_id": result["draw"]["tweetId"], "tweet_url": result["draw"]["tweetUrl"], "status": result["draw"]["status"], "primary_winners": [ winner for winner in result["winners"] if not winner["isBackup"] ], "backup_winners": [ winner for winner in result["winners"] if winner["isBackup"] ], } print(verification) ``` ```go Go theme={null} package main import ( "fmt" "io" "log" "net/http" ) func main() { drawID := "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345" req, err := http.NewRequest("GET", "https://xquik.com/api/v1/draws/"+drawID, nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { log.Fatalf("giveaway draw failed with %d: %s", resp.StatusCode, string(body)) } fmt.Println(string(body)) } ``` ## Interpret Twitter Giveaway Winner Rows Winner order is part of the result. Preserve each 1-indexed `position` instead of sorting by username or tweet ID. `isBackup` is `false`. Use the original `position` in announcements and fulfillment records. `isBackup` is `true`. Keep the row separate until replacement approval is recorded. `tweetId` identifies the selected reply. Store it beside the winner's X username. `authorUsername` identifies the selected X account at draw time. Keep the tweet ID as the stable join value. Do not promote a backup winner by changing the existing row. Record the replacement decision separately. This preserves the original draw result. ## Preserve Eligibility Rules Separately `GET /draws/{id}` returns the draw snapshot and winners. It does not repeat the eligibility filters sent to `POST /draws`. Store the original create request when reviews need these settings: * required repost and followed-account rules * required keywords, hashtags, mentions, or language * minimum account age and follower count * primary, backup, and unique-author settings Use an entry export when the review needs candidate rows. The export can show whether an entry passed the filters. It does not reconstruct a missing create request. ## Build a Giveaway Verification Handoff Join the draw summary, winner rows, and export location with one draw ID: ```json theme={null} { "draw_id": "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "tweet_id": "1893456789012345678", "tweet_url": "https://x.com/xquik/status/1893456789012345678", "status": "completed", "total_entries": 847, "valid_entries": 312, "created_at": "2026-02-24T10:00:00.000Z", "drawn_at": "2026-02-24T10:05:00.000Z", "primary_winner_count": 3, "backup_winner_count": 0, "winner_export_path": "/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345/export?format=csv&type=winners", "entry_export_path": "/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345/export?format=csv&type=entries" } ``` Store winner rows separately from this summary. Keep `position` and `isBackup` on every row. Never infer filter rules from entry counts. ## Path Parameters The draw public ID. Returned when you [create a draw](/api-reference/draws/create) or [list draws](/api-reference/draws/twitter-giveaway-history). ## Headers Send your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. ## Response ### 200 OK Draw details including tweet metadata. **Draw object fields:** Draw public ID returned by Xquik. X tweet ID. Original tweet URL. Full text content of the tweet. Username of the tweet author. Like count at time of draw. Retweet count at time of draw. Reply count at time of draw. Quote tweet count at time of draw. Draw status (e.g. `completed`). Inspected candidate entries. Entries that passed all filters. ISO 8601 creation timestamp. ISO 8601 timestamp of when winners were selected. Present only for completed draws. Selected winners and backup winners ordered by position. **Winner object fields:** Winner position (1-indexed). X username of the winner. Tweet ID of the winning reply. `true` if this is a backup winner. ```json theme={null} { "draw": { "id": "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "tweetId": "1893456789012345678", "tweetUrl": "https://x.com/xquik/status/1893456789012345678", "tweetText": "Giveaway time! Reply to enter. 3 winners get a free month of Xquik Pro.", "tweetAuthorUsername": "xquik", "tweetLikeCount": 2450, "tweetRetweetCount": 1820, "tweetReplyCount": 847, "tweetQuoteCount": 95, "status": "completed", "totalEntries": 847, "validEntries": 312, "createdAt": "2026-02-24T10:00:00.000Z", "drawnAt": "2026-02-24T10:05:00.000Z" }, "winners": [ { "position": 1, "authorUsername": "alice_web3", "tweetId": "1893456789012345700", "isBackup": false }, { "position": 2, "authorUsername": "bob_dev", "tweetId": "1893456789012345701", "isBackup": false }, { "position": 3, "authorUsername": "charlie_nft", "tweetId": "1893456789012345702", "isBackup": false } ] } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` The API key or OAuth bearer token is missing or invalid. Replace it before retrying. ### 404 Not Found ```json theme={null} { "error": "not_found" } ``` No draw exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Handle Giveaway Draw Responses Read `draw` and `winners`. Split primary and backup winners before handoff. Authentication failed. Replace the API key or OAuth bearer token first. No accessible draw matches that ID. Check its account and exact value. Too many requests. Honor `Retry-After`, then retry the same draw ID. ## Twitter Giveaway Winner Verification Questions ### How Do I Verify a Past Twitter Giveaway Winner? Get the draw ID from giveaway history. Request `GET /draws/{id}`. Preserve the ordered winner rows and source tweet snapshot. ### Does the Draw Detail Include Eligibility Rules? No. Store the original create request separately. Use entry exports when a review needs candidate pass or fail values. ### How Do I Identify Backup Winners? Read `isBackup` on every winner row. A primary winner has `false`. A backup winner has `true`. ### Can I Download the Giveaway Result? Yes. Call `GET /draws/{id}/export`. Choose winners or entries and a supported file format. ### What If the Giveaway Draw ID Returns 404? Check the exact draw ID and authenticated account. Another account's draw is not accessible with the current credential. ### Does Winner Verification Consume Credits? No. Retrieving an existing draw is free. Running a new draw can consume credits. **Related:** [Export Draw](/api-reference/draws/export) downloads results. [Giveaway History](/api-reference/draws/twitter-giveaway-history) lists past draws. [Create Draw](/api-reference/draws/create) runs a new selection. # Twitter Giveaway History API & Past Draw Results Source: https://docs.xquik.com/api-reference/draws/twitter-giveaway-history GET /draws List past Twitter giveaway draws with tweet URLs, statuses, entry counts, timestamps, and opaque cursors. Then retrieve winners or export audit records. ```json theme={null} { "draws": [], "hasMore": false } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
List every Twitter giveaway draw owned by the authenticated Xquik account. Review source tweets, draw statuses, entry counts, and completion times. Continue through older results with an opaque cursor. This endpoint returns draw summaries. It does not return winner objects or eligibility rules. Retrieve one draw when winner verification needs those details. **Free** - does not consume credits ## List Past Twitter Giveaway Draws Use `GET /draws` for a Twitter giveaway history page or audit queue. Results are ordered by `createdAt` and draw ID, newest first. Each row identifies its source tweet and inspected candidate counts. Call `GET /draws` for draw IDs, tweet URLs, statuses, entry counts, and timestamps. Follow `nextCursor` when `hasMore` is true. Call `GET /draws/{id}` for source tweet metrics and ordered winner rows. Keep primary and backup winners separate. Call `GET /draws/{id}/export` for winners or entries. Choose CSV, JSON, Markdown, PDF, text, or XLSX. Call `POST /draws` with the source tweet and eligibility rules. Do not reuse an old draw ID for a new selection. Use list results for discovery. Use detail results for winner verification. Use exports for review, customer support, or campaign archives. ```bash cURL theme={null} curl --fail-with-body "https://xquik.com/api/v1/draws?limit=10" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/draws?limit=10", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, } ); const page = await response.json(); if (!response.ok) { throw new Error(page.message || "Giveaway history request failed."); } const drawRows = page.draws.map((draw) => ({ draw_id: draw.id, tweet_url: draw.tweetUrl, status: draw.status, total_entries: draw.totalEntries, valid_entries: draw.validEntries, created_at: draw.createdAt, drawn_at: draw.drawnAt || null, })); process.stdout.write( `${JSON.stringify({ drawRows, hasMore: page.hasMore, nextCursor: page.nextCursor || null })}\n`, ); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/draws", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"limit": 10}, ) response.raise_for_status() page = response.json() draw_rows = [ { "draw_id": draw["id"], "tweet_url": draw["tweetUrl"], "status": draw["status"], "total_entries": draw["totalEntries"], "valid_entries": draw["validEntries"], "created_at": draw["createdAt"], "drawn_at": draw.get("drawnAt"), } for draw in page["draws"] ] print({"draws": draw_rows, "next_cursor": page.get("nextCursor")}) ``` ```go Go theme={null} package main import ( "fmt" "io" "log" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/draws?limit=10", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { log.Fatalf("giveaway history failed with %d: %s", resp.StatusCode, string(body)) } fmt.Println(string(body)) } ``` ## Choose the Correct Giveaway Result The list route answers which draws exist. It also shows when each draw ran. It cannot answer who won or which reply qualified. Use this handoff for each returned row: Call `GET /draws`. Preserve `id`, `tweetUrl`, `status`, and `createdAt`. Read `totalEntries` and `validEntries` from each list summary. Call `GET /draws/{id}`. Preserve position, username, tweet ID, and backup state. Read the tweet ID, text, author, and engagement counts from draw detail. Call `GET /draws/{id}/export`. Select `winners` or `entries` and one format. Do not label `validEntries` as a winner count. It counts inspected entries that passed the configured filters. Winner rows exist only in the detail response. ## Build a Twitter Giveaway Audit Handoff Store one summary row per draw. Keep the public draw ID as the join key. Preserve the source tweet URL instead of extracting a mutable username. ```json theme={null} { "draw_id": "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "tweet_url": "https://x.com/xquik/status/1893456789012345678", "status": "completed", "total_entries": 847, "valid_entries": 312, "created_at": "2026-02-24T10:00:00.000Z", "drawn_at": "2026-02-24T10:05:00.000Z", "detail_path": "/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "winner_export_path": "/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345/export?format=csv&type=winners" } ``` Fetch detail rows before publishing past giveaway winners. Preserve winner position and `isBackup`. This prevents backup winners from appearing as primary winners in dashboards or announcements. ## Query Parameters Results per page. Default `50`, max `100`. Cursor for pagination. Pass the `nextCursor` value from a previous response to fetch the next page. ## Headers Send your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. ## Response ### 200 OK List of draw objects ordered by creation date (newest first). **Draw object fields:** Draw public ID returned by Xquik. Original tweet URL used for the draw. Draw status (e.g. `completed`). Total replies collected from the tweet. Entries that passed all filters. ISO 8601 timestamp of when the draw was created. ISO 8601 timestamp of when winners were selected. Present only for completed draws. `true` if additional pages exist beyond this result set. Pagination cursor. Pass it as `cursor` for the next page. ```json theme={null} { "draws": [ { "id": "f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345", "tweetUrl": "https://x.com/xquik/status/1893456789012345678", "status": "completed", "totalEntries": 847, "validEntries": 312, "createdAt": "2026-02-24T10:00:00.000Z", "drawnAt": "2026-02-24T10:05:00.000Z" }, { "id": "9a78ce15-2f3d-4f90-a86b-1049c7a26e92", "tweetUrl": "https://x.com/xquik/status/1893456789012340000", "status": "completed", "totalEntries": 1240, "validEntries": 980, "createdAt": "2026-02-23T16:30:00.000Z", "drawnAt": "2026-02-23T16:35:00.000Z" } ], "hasMore": true, "nextCursor": "MjAyNi0wMi0yM1QxNjozMDowMC4wMDBafDY2NjY1" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key / session cookie. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Paginate Twitter Giveaway History Draws use cursor pagination. Pass `nextCursor` as the next `cursor` value. ```bash First page theme={null} curl --fail-with-body "https://xquik.com/api/v1/draws?limit=10" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```bash Next page theme={null} curl --fail-with-body "https://xquik.com/api/v1/draws?limit=10&cursor=MjAyNi0wMi0yM1QxNjozMDowMC4wMDBafDY2NjY1" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Continue fetching pages until `hasMore` is `false`. Cursors are opaque strings. Do not parse or construct them manually. Use a stable loop for a complete history export: ```javascript Node.js theme={null} const seenCursors = new Set(); const draws = []; let cursor; do { const url = new URL("https://xquik.com/api/v1/draws"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const response = await fetch(url, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const page = await response.json(); if (!response.ok) { throw new Error(page.message || "Giveaway history page failed."); } draws.push(...page.draws); const next = page.nextCursor; if (page.hasMore && (!next || seenCursors.has(next))) { throw new Error("Giveaway history cursor did not advance."); } if (next) seenCursors.add(next); cursor = page.hasMore ? next : undefined; } while (cursor); process.stdout.write(`${JSON.stringify(draws)}\n`); ``` Save the last accepted cursor after each durable batch. Restart from that cursor after a worker failure. Never build a cursor from timestamps or draw IDs. ## Handle Giveaway History Responses Read `draws` and `hasMore`. Read `nextCursor` only when another page exists. Authentication failed. Replace the API key or OAuth bearer token first. Too many requests. Honor `Retry-After`, then resume with the same cursor. Do not advance the cursor after a failed request. A retry must request the same page. Append results only after the response succeeds. ## Twitter Giveaway History Questions ### How Do I View Past Twitter Giveaway Draws? Call `GET /draws` with your Xquik API key. The newest draw summaries appear first. Follow `nextCursor` for older results. ### Does Giveaway History Include Past Winners? The list response does not include winners. Pass its `id` to `GET /draws/{id}` for primary and backup winner rows. ### Can I Download Previous Twitter Giveaway Winners? Yes. Fetch the draw ID first. Then export `type=winners` in CSV, JSON, Markdown, PDF, text, or XLSX. ### What Do Total and Valid Entries Mean? `totalEntries` counts inspected candidate entries. `validEntries` counts entries that passed the draw filters. Neither field reports the winner count. ### How Do I Audit a Twitter Giveaway Result? Store the list summary and draw ID. Fetch draw details and preserve winner order. Export entries when the review needs candidate-level evidence. ### Does Listing Giveaway Draws Consume Credits? No. Listing draw history is free. Running a new draw can consume credits. **Related:** [Get Draw](/api-reference/draws/get) retrieves one result. Use [Create Draw](/api-reference/draws/create) for a new winner selection. # Get Webhook Event & Monitor Payload Details Source: https://docs.xquik.com/api-reference/events/get GET /events/{id} Retrieve one monitor event by ID with tweet text, author fields, engagement metrics, event type, timestamps, and monitor identifiers. See event fields. ```json theme={null} { "id": "42", "type": "tweet.new", "username": "elonmusk", "monitorId": "7", "monitorType": "account", "occurredAt": "2025-01-15T12:00:00Z", "data": { "tweetId": "1234567890" } } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl https://xquik.com/api/v1/events/9001 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '{ event_id: .id, event_type: .type, monitor_type: .monitorType, monitor_id: .monitorId, keyword_monitor_id: (.keywordMonitorId // null), username: (.username // null), query: (.query // null), occurred_at: .occurredAt, x_event_id: (.xEventId // null), tweet_id: (.xEventId // .data.id // null), tweet_text: (.data.text // null), author_username: (.data.author.userName // null), event_detail_endpoint: ("/api/v1/events/" + .id), delivery_join_key: .id }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/events/9001", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const event = await response.json(); const eventRow = { event_id: event.id, event_type: event.type, monitor_type: event.monitorType, monitor_id: event.monitorId, keyword_monitor_id: event.keywordMonitorId ?? null, username: event.username ?? null, query: event.query ?? null, occurred_at: event.occurredAt, x_event_id: event.xEventId ?? null, tweet_id: event.xEventId ?? event.data?.id ?? null, tweet_text: event.data?.text ?? null, author_username: event.data?.author?.userName ?? null, event_detail_endpoint: `/api/v1/events/${event.id}`, delivery_join_key: event.id, }; process.stdout.write(`${JSON.stringify(eventRow)}\n`); ``` ```python Python theme={null} import json import requests response = requests.get( "https://xquik.com/api/v1/events/9001", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) event = response.json() tweet = event.get("data") if isinstance(event.get("data"), dict) else {} author = tweet.get("author") if isinstance(tweet.get("author"), dict) else {} event_row = { "event_id": event["id"], "event_type": event["type"], "monitor_type": event["monitorType"], "monitor_id": event["monitorId"], "keyword_monitor_id": event.get("keywordMonitorId"), "username": event.get("username"), "query": event.get("query"), "occurred_at": event["occurredAt"], "x_event_id": event.get("xEventId"), "tweet_id": event.get("xEventId") or tweet.get("id"), "tweet_text": tweet.get("text"), "author_username": author.get("userName"), "event_detail_endpoint": f"/api/v1/events/{event['id']}", "delivery_join_key": event["id"], } print(json.dumps(event_row)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type EventAuthor struct { UserName *string `json:"userName"` } type EventPayload struct { ID *string `json:"id"` Text *string `json:"text"` Author *EventAuthor `json:"author"` } type Event struct { ID string `json:"id"` Type string `json:"type"` MonitorType string `json:"monitorType"` MonitorID string `json:"monitorId"` KeywordMonitorID *string `json:"keywordMonitorId"` Username *string `json:"username"` Query *string `json:"query"` OccurredAt string `json:"occurredAt"` Data EventPayload `json:"data"` XEventID *string `json:"xEventId"` } type EventRow struct { DeliveryJoinKey string `json:"delivery_join_key"` EventDetailEndpoint string `json:"event_detail_endpoint"` EventID string `json:"event_id"` EventType string `json:"event_type"` MonitorType string `json:"monitor_type"` MonitorID string `json:"monitor_id"` KeywordMonitorID *string `json:"keyword_monitor_id"` Username *string `json:"username"` Query *string `json:"query"` OccurredAt string `json:"occurred_at"` XEventID *string `json:"x_event_id"` TweetID *string `json:"tweet_id"` TweetText *string `json:"tweet_text"` AuthorUsername *string `json:"author_username"` } func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/events/9001", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var event Event if err := json.NewDecoder(resp.Body).Decode(&event); err != nil { log.Fatal(err) } tweetID := event.XEventID if tweetID == nil { tweetID = event.Data.ID } var authorUsername *string if event.Data.Author != nil { authorUsername = event.Data.Author.UserName } row := EventRow{ DeliveryJoinKey: event.ID, EventDetailEndpoint: "/api/v1/events/" + event.ID, EventID: event.ID, EventType: event.Type, MonitorType: event.MonitorType, MonitorID: event.MonitorID, KeywordMonitorID: event.KeywordMonitorID, Username: event.Username, Query: event.Query, OccurredAt: event.OccurredAt, XEventID: event.XEventID, TweetID: tweetID, TweetText: event.Data.Text, AuthorUsername: authorUsername, } if err := json.NewEncoder(os.Stdout).Encode(row); err != nil { log.Fatal(err) } } ``` The Node.js, Python, and Go examples convert one event into an audit row. Store `event_id`, `monitor_type`, `monitor_id`, `event_type`, `occurred_at`, `x_event_id`, tweet fields from `data`, `event_detail_endpoint`, and `delivery_join_key`; keyword monitor events use `keyword_monitor_id` and `query` instead of `username`. ## Event detail handoff Use this endpoint after list or delivery workflows find an event that needs full tweet data, author context, or support/audit review. | Event detail column | Response source | Handoff rule | | ------------------- | ------------------------------ | -------------------------------------------------- | | Event ID | `id` | Use this ID for event detail and delivery joins. | | Event type | `type` | Route tweet, reply, quote, or repost processing. | | Monitor source | `monitorType` | Separate account and keyword monitor events. | | Account monitor | `monitorId` and `username` | Join the event to its tracked X account. | | Keyword monitor | `keywordMonitorId` and `query` | Join the event to its tracked X search. | | Tweet ID | `xEventId` | Store the X tweet identifier separately. | | Occurred at | `occurredAt` | Preserve the original event timestamp. | | Tweet payload | `data` | Extract tweet text, author, and engagement fields. | Store `event_id`, `event_type`, `occurred_at`, `event_detail_endpoint`, and `delivery_join_key`. Keep `event_id` as the Xquik event identifier. Match `delivery_join_key` to webhook delivery `streamEventId` from [List Deliveries](/api-reference/webhooks/deliveries). Do not use `x_event_id` for delivery joins. Store `monitor_type`, `monitor_id`, `keyword_monitor_id`, `username`, and `query` so account and keyword monitor audits stay separate. Store `x_event_id`, `tweet_id`, `tweet_text`, and `author_username` from `data` when the event contains tweet content. After joining deliveries, attach `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt` to the event detail row. ## Path parameters The event ID to retrieve. ## Headers Your API key. ## Response ### 200 OK Unique event identifier. Event type (e.g. `tweet.new`, `tweet.reply`, `tweet.quote`, `tweet.retweet`). Account monitor ID for account events, or keyword monitor ID for keyword events. Source type: `account` or `keyword`. Keyword monitor ID. Present only for keyword monitor events. X username that triggered the event. Present only for account monitor events. Keyword query that matched the event. Present only for keyword monitor events. ISO 8601 timestamp of when the event occurred on X. Tweet object stored with the monitor event. Fields vary by event type. See [Webhooks Overview](/webhooks/overview). X platform tweet ID associated with this event. Present only for tweet events. ```json theme={null} { "id": "9001", "type": "tweet.new", "monitorId": "7", "monitorType": "account", "username": "elonmusk", "occurredAt": "2026-02-24T14:22:00.000Z", "data": { "id": "1893456789012345678", "text": "The future is now.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": false, "isReply": false, "isQuote": false, "createdAt": "2026-02-24T14:22:00.000Z" }, "xEventId": "1893456789012345678" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. Check the `x-api-key` header value. ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided event ID is not a valid format. ### 404 Not Found ```json theme={null} { "error": "not_found" } ``` No event exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [List Events](/api-reference/events/list) · [List Deliveries](/api-reference/webhooks/deliveries) · [Get Monitor](/api-reference/monitors/twitter-account-monitor-status) # List Webhook Events & Replay Monitor Activity Source: https://docs.xquik.com/api-reference/events/list GET /events Query stored tweet, follower, following, profile, and keyword monitor events by monitor, event type, time range, and cursor. Includes response fields. ```json theme={null} { "events": [], "hasMore": false } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Listing stored events is free. Event and webhook deliveries are included in active monitor billing. Stored monitor events remain available for 90 days. Export every page before older records expire. The dashboard does not currently provide direct CSV export for monitor events. ```bash cURL theme={null} curl "https://xquik.com/api/v1/events?limit=10&monitorId=7" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '. as $page | .events[] | { event_id: .id, event_type: .type, monitor_type: .monitorType, monitor_id: .monitorId, keyword_monitor_id: (.keywordMonitorId // null), username: (.username // null), query: (.query // null), occurred_at: .occurredAt, tweet_id: (.data.id // null), tweet_text: (.data.text // null), author_username: (.data.author.userName // null), event_detail_endpoint: ("/api/v1/events/" + .id), delivery_join_key: .id, has_more: $page.hasMore, next_cursor: ($page.nextCursor // null) }' ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/events?limit=10&monitorId=7", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, } ); const data = await response.json(); const eventRows = data.events.map((event) => ({ event_id: event.id, event_type: event.type, monitor_type: event.monitorType, monitor_id: event.monitorId, keyword_monitor_id: event.keywordMonitorId ?? null, username: event.username ?? null, query: event.query ?? null, occurred_at: event.occurredAt, tweet_id: event.data?.id ?? null, tweet_text: event.data?.text ?? null, author_username: event.data?.author?.userName ?? null, event_detail_endpoint: `/api/v1/events/${event.id}`, delivery_join_key: event.id, has_more: data.hasMore, next_cursor: data.nextCursor ?? null, })); for (const row of eventRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } ``` ```python Python theme={null} import json import requests response = requests.get( "https://xquik.com/api/v1/events", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"limit": 10, "monitorId": "7"}, ) data = response.json() event_rows = [] for event in data["events"]: tweet = event.get("data") if isinstance(event.get("data"), dict) else {} author = tweet.get("author") if isinstance(tweet.get("author"), dict) else {} event_rows.append( { "event_id": event["id"], "event_type": event["type"], "monitor_type": event["monitorType"], "monitor_id": event["monitorId"], "keyword_monitor_id": event.get("keywordMonitorId"), "username": event.get("username"), "query": event.get("query"), "occurred_at": event["occurredAt"], "tweet_id": tweet.get("id"), "tweet_text": tweet.get("text"), "author_username": author.get("userName"), "event_detail_endpoint": f"/api/v1/events/{event['id']}", "delivery_join_key": event["id"], "has_more": data["hasMore"], "next_cursor": data.get("nextCursor"), } ) for row in event_rows: print(json.dumps(row)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type EventAuthor struct { UserName *string `json:"userName"` } type EventPayload struct { ID *string `json:"id"` Text *string `json:"text"` Author *EventAuthor `json:"author"` } type Event struct { ID string `json:"id"` Type string `json:"type"` MonitorType string `json:"monitorType"` MonitorID string `json:"monitorId"` KeywordMonitorID *string `json:"keywordMonitorId"` Username *string `json:"username"` Query *string `json:"query"` OccurredAt string `json:"occurredAt"` Data EventPayload `json:"data"` } type EventResponse struct { Events []Event `json:"events"` HasMore bool `json:"hasMore"` NextCursor *string `json:"nextCursor"` } type EventRow struct { DeliveryJoinKey string `json:"delivery_join_key"` EventDetailEndpoint string `json:"event_detail_endpoint"` EventID string `json:"event_id"` EventType string `json:"event_type"` MonitorType string `json:"monitor_type"` MonitorID string `json:"monitor_id"` KeywordMonitorID *string `json:"keyword_monitor_id"` Username *string `json:"username"` Query *string `json:"query"` OccurredAt string `json:"occurred_at"` TweetID *string `json:"tweet_id"` TweetText *string `json:"tweet_text"` AuthorUsername *string `json:"author_username"` HasMore bool `json:"has_more"` NextCursor *string `json:"next_cursor"` } func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/events?limit=10&monitorId=7", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var data EventResponse if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { log.Fatal(err) } encoder := json.NewEncoder(os.Stdout) for _, event := range data.Events { var authorUsername *string if event.Data.Author != nil { authorUsername = event.Data.Author.UserName } row := EventRow{ DeliveryJoinKey: event.ID, EventDetailEndpoint: "/api/v1/events/" + event.ID, EventID: event.ID, EventType: event.Type, MonitorType: event.MonitorType, MonitorID: event.MonitorID, KeywordMonitorID: event.KeywordMonitorID, Username: event.Username, Query: event.Query, OccurredAt: event.OccurredAt, TweetID: event.Data.ID, TweetText: event.Data.Text, AuthorUsername: authorUsername, HasMore: data.HasMore, NextCursor: data.NextCursor, } if err := encoder.Encode(row); err != nil { log.Fatal(err) } } } ``` These examples emit one stored event row per line. Store `event_id`, `monitor_type`, `monitor_id`, `event_type`, `occurred_at`, tweet fields from `data`, `event_detail_endpoint`, `delivery_join_key`, and `next_cursor`; pass `nextCursor` as `cursor` until `hasMore` is `false`. ## Source filter examples Use `monitorId` for account monitor events and `keywordMonitorId` for keyword monitor events. Do not pass a keyword monitor ID as `monitorId`; that filter matches account monitor events only. ```bash Account monitor events theme={null} curl "https://xquik.com/api/v1/events?limit=50&monitorId=7" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '{events: [.events[] | {id, type, monitorId, username}], hasMore, nextCursor}' ``` ```bash Keyword monitor events theme={null} curl "https://xquik.com/api/v1/events?limit=50&keywordMonitorId=21" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '{events: [.events[] | {id, type, keywordMonitorId, query}], hasMore, nextCursor}' ``` ## Query parameters Results per page. Default `50`, max `100`. Filter account-monitor events by account monitor ID. Use `keywordMonitorId` for keyword-monitor events. Omit both filters to return events from all monitors. Filter keyword-monitor events by keyword monitor ID. Use the `id` returned by keyword monitor endpoints. Filter by event type. Valid types: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, `tweet.longform`, `profile.avatar.changed`, `profile.banner.changed`, `profile.name.changed`, `profile.username.changed`, `profile.bio.changed`, `profile.location.changed`, `profile.url.changed`, `profile.verified.changed`, `profile.protected.changed`, `profile.pinned_tweet.changed`, `profile.unavailable.changed`. Omit to return all types. Cursor for pagination. Pass the `nextCursor` value from a previous response to fetch the next page. ## Headers Your API key. ## Response ### 200 OK List of event objects matching the query. **Event object fields:** Unique event identifier. Event type from the monitor event type list. Account monitor ID for account events, or keyword monitor ID for keyword events. Source type: `account` or `keyword`. Keyword monitor ID. Present only for keyword monitor events. X username that triggered the event. Present only for account monitor events. Keyword query that matched the event. Present only for keyword monitor events. ISO 8601 timestamp of when the event occurred on X. Tweet object stored with the monitor event. Fields vary by event type. The `data` field contains the raw tweet object. See [Webhooks Overview](/webhooks/overview) for the full data schema of each event type. `true` if additional pages exist beyond this result set. Pagination cursor. Pass it as `cursor` for the next page. ```json theme={null} { "events": [ { "id": "9001", "type": "tweet.new", "monitorId": "7", "monitorType": "account", "username": "elonmusk", "occurredAt": "2026-02-24T14:22:00.000Z", "data": { "id": "1893456789012345678", "text": "The future is now.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": false, "isReply": false, "isQuote": false, "createdAt": "2026-02-24T14:22:00.000Z" } }, { "id": "9002", "type": "tweet.reply", "monitorId": "7", "monitorType": "account", "username": "elonmusk", "occurredAt": "2026-02-24T15:05:30.000Z", "data": { "id": "1893456789012345999", "text": "Absolutely. Shipping next week.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": false, "isReply": true, "isQuote": false, "inReplyToId": "1893456789012345900", "createdAt": "2026-02-24T15:05:30.000Z" } } ], "hasMore": true, "nextCursor": "MjAyNi0wMi0yNFQxNTowNTozMC4wMDBafDkwMDI=" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. Check the `x-api-key` header value. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Event inventory handoff Use this endpoint when an agent, dashboard, or support workflow needs a compact inventory of stored monitor events before looking up details or webhook delivery status. | Event inventory column | Response source | Pagination rule | | ---------------------- | --------------------------------------- | ----------------------------------------------- | | Event ID | `events[].id` | Open one event or join a webhook delivery. | | Event type | `events[].type` | Route tweet, reply, quote, or repost records. | | Monitor source | `events[].monitorType` | Separate account and keyword monitor rows. | | Account source | `events[].monitorId` and `username` | Filter one tracked X account. | | Keyword source | `events[].keywordMonitorId` and `query` | Filter one tracked X search. | | Tweet fields | `events[].data` | Export tweet text, author, and timestamps. | | More pages | `hasMore` | Continue only while this value is `true`. | | Next page | `nextCursor` | Pass this value as the next `cursor` parameter. | Store `event_id`, `event_type`, `occurred_at`, and `event_detail_endpoint`. Use [Get Event](/api-reference/events/get) when a later step needs the full event payload. Store `monitor_type`, `monitor_id`, `keyword_monitor_id`, `username`, and `query` so account and keyword monitor events stay separate in exports. Use `monitorId` for account-monitor filters and `keywordMonitorId` for keyword-monitor filters. Store `tweet_id`, `tweet_text`, and `author_username` from `data` when the event contains tweet content. Other event types can leave those fields empty. Store `delivery_join_key` as the event ID. In webhook delivery rows, match it to `streamEventId` from [List Deliveries](/api-reference/webhooks/deliveries). After joining deliveries, store `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt` with the event row. Store `next_cursor` only when `has_more` is `true`. Pass it as `cursor` for the next page and stop when `has_more` is `false`. ## Pagination Events use cursor pagination. Pass `nextCursor` as the next `cursor` value. ```bash First page theme={null} curl "https://xquik.com/api/v1/events?limit=10&monitorId=7" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '{event_ids: [.events[].id], has_more: .hasMore, next_cursor: (.nextCursor // null)}' ``` ```bash Next page theme={null} curl "https://xquik.com/api/v1/events?limit=10&monitorId=7&cursor=MjAyNi0wMi0yNFQxNTowNTozMC4wMDBafDkwMDI=" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '{event_ids: [.events[].id], has_more: .hasMore, next_cursor: (.nextCursor // null)}' ``` Continue fetching pages until `hasMore` is `false`. Cursors are opaque strings. Do not parse or construct them manually. **Related:** [Get Event](/api-reference/events/get) · [List Deliveries](/api-reference/webhooks/deliveries) · [List Monitors](/api-reference/monitors/list) # Twitter Scraper API & Bulk Extraction Jobs Source: https://docs.xquik.com/api-reference/extractions/create POST /extractions Start an export job for tweets, followers, following, replies, profiles, timelines, media, communities, or lists with one of 23 tools. See API fields. ```json theme={null} { "allowed": true, "creditsAvailable": "10000", "creditsRequired": "1000", "estimatedResults": 1000, "source": "profile" } ``` ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "toolType": "follower_explorer", "status": "running" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
**1 credit per result extracted** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ## Query Parameters Return a cost estimate without creating a job. Defaults to `false`. ```bash cURL theme={null} 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 ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/extractions", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ toolType: "reply_extractor", targetTweetId: "1893704267862470862", resultsLimit: 500, }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/extractions", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "toolType": "reply_extractor", "targetTweetId": "1893704267862470862", "resultsLimit": 500, }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "toolType": "reply_extractor", "targetTweetId": "1893704267862470862", "resultsLimit": 500, }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/extractions", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Headers Your API key. Session cookie authentication is also supported. Must be `application/json`. ## Body Extraction tool to run or estimate. See the endpoint's tool list. ### Single Targets Tweet ID for a tweet-centered extraction. Username for an account-centered extraction. You may include `@`. Community ID for a community extraction. List ID for a list extraction. Space ID for `space_explorer`. Query for `tweet_search_extractor` or `community_search`. ### Collection Targets Process 1-10,000 Tweet IDs in one collection job. Process 1-100 unique usernames in one collection job. Process 1-100 unique community IDs in one collection job. Process 1-100 unique List IDs in one collection job. Process 1-100 unique search queries in one collection job. Process up to 10,000 mixed targets with automatic routing. Each target accepts a supported string or `{ "kind": "...", "value": "..." }`. Process up to 100 profile relations in one collection job. Each relation target uses `{ "relation": "...", "value": "..." }`. ### Collection Controls Search ranking: `Latest`, `Top`, or `Both`. Defaults to `Latest`. Stop after this many results. Omit it to collect all available results. Maximum results collected for each target. Minimum: `1`. Reply pages collected per target. Range: `1-1,000`. Resume one reply target from this cursor. Merge duplicates across targets. Defaults to `true`. Duplicate handling: `none`, `first`, or `merge`. Use `dedupeMode=merge`. Defaults to `false`. Add matched search terms to collection metadata. Defaults to `false`. Add source target metadata to each result. Defaults to `true`. ### Reply Collection Strategy: `auto`, `complete`, `direct`, `search`, or `thread`. Reply scope: `all`, `direct`, or `nested`. Defaults to `all`. Maximum nested reply depth. Minimum: `1`. Order: `relevance`, `latest`, `oldest`, or `likes`. Exclude replies from the source author. Defaults to `false`. Include the source post. Defaults to `false`. Return only replies with media. Defaults to `false`. Reply start time as ISO 8601 or Unix seconds. Reply end time as ISO 8601 or Unix seconds. ### Tweet Result Filters Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Maximum Tweet repost count. Maximum Tweet reply count. Maximum Tweet quote count. Return only Blue-verified Tweet authors. Defaults to `false`. Match the Tweet card name. Match the source application. Exclude a source application. Match latitude, longitude, and radius. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match a place name. Set the radius for `near`. Match Tweets inside a recent time window. Return only native reposts. Defaults to `false`. Enable safe search. Defaults to `false`. Return only news results. Defaults to `false`. ### Profile Result Filters Minimum profile follower count. Maximum profile follower count. Minimum profile following count. Maximum profile following count. Minimum profile post count. Maximum profile post count. Minimum profile age in days. Match the exact profile verification type. Require a profile website. Defaults to `false`. Require a profile location. Defaults to `false`. Require bio terms separated by commas or lines. Require matching profile location text. Require matching username text. ### Tweet Search Filters These fields apply to `tweet_search_extractor`. Match an author username without `@`. Match replies sent to a username. Match Tweets mentioning a username. Match a language code, such as `en`. Include Tweets on or after `YYYY-MM-DD`. Include Tweets before `YYYY-MM-DD`. Media: `images`, `videos`, `gifs`, `media`, `links`, or `none`. Minimum like count. Minimum repost count. Minimum reply count. Minimum quote count. Return only verified authors. Reply mode: `include`, `exclude`, or `only`. Repost mode: `include`, `exclude`, or `only`. Quote mode: `include`, `exclude`, or `only`. Match one exact phrase. Exclude words or quoted phrases. Match any listed word or quoted phrase. Match hashtags separated by spaces, commas, or lines. Match cashtags separated by spaces, commas, or lines. Match a URL substring or domain. Match a conversation ID. Return only replies to this Tweet ID. Return only quotes of this Tweet ID. Return only reposts of this Tweet ID. Search within a List ID. Search within a place ID. Search within a country code. Set a geographic center and radius. Set a geographic bounding box. Append raw advanced search syntax. ## Tool types Each extraction job needs one target field based on `toolType`. `resultsLimit` works with every type when you want to cap returned rows. Use `targetTweetId` for tweet-centered jobs: * `article_extractor` extracts article content from a tweet. * `favoriters` extracts visible users who liked a post. Liker identities can be unavailable even when the post reports likes. * `quote_extractor` extracts users who quote-tweeted a tweet. * `reply_extractor` extracts users who replied to a tweet. * `repost_extractor` extracts users who retweeted a tweet. * `thread_extractor` extracts all tweets in a thread. Use `targetUsername` for account-centered jobs: * `follower_explorer` extracts followers of an account. * `following_explorer` extracts accounts followed by a user. * `mention_extractor` extracts tweets mentioning an account. * `post_extractor` extracts posts from an account. * `user_likes` extracts tweets liked by a user. * `user_media` extracts media posts from a user. * `verified_follower_explorer` extracts verified followers of an account. Use `targetCommunityId` for community jobs: * `community_extractor` extracts members of a community. * `community_moderator_explorer` extracts moderators of a community. * `community_post_extractor` extracts posts from a community. * `community_search` searches matching posts within that community and also requires `searchQuery`. Use `searchQuery` for keyword jobs: * `people_search` searches for users by keyword. * `tweet_search_extractor` searches and extracts tweets by keyword or hashtag. Use `targetListId` for X List jobs: * `list_follower_explorer` extracts followers of a list. * `list_member_extractor` extracts members of a list. * `list_post_extractor` extracts posts from a list. Use `targetSpaceId` for Space jobs: * `space_explorer` extracts participants of a Space. Store `targetSpaceId` beside the returned extraction `id`. Poll [Get Extraction](/api-reference/extractions/twitter-extraction-results) or use [Export Extraction](/api-reference/extractions/export) after completion to read participant user rows. ## Response ### 202 Accepted Unique extraction job ID (UUID). Tool type used for this extraction. Job status. `running` when first created. ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "toolType": "reply_extractor", "status": "running" } ``` Extraction runs asynchronously. Poll [Get Extraction](/api-reference/extractions/twitter-extraction-results) until status is `completed` or `failed`. ### 400 Invalid input ```json theme={null} { "error": "invalid_input", "message": "Missing or malformed request body" } ``` Request body is missing or malformed. Ensure all required fields are present. ### 400 Invalid tool type ```json theme={null} { "error": "invalid_tool_type", "message": "Unrecognized tool type" } ``` The `toolType` value is not one of the 23 supported tools. See [tool types](#tool-types). ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 402 Insufficient credits ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` The available balance cannot cover the extraction. Possible error values include `no_subscription`, `subscription_inactive`, `no_credits`, and `insufficient_credits`. ### 404 Not Found ```json theme={null} { "error": "tweet_not_found", "message": "Tweet not found" } ``` The target tweet does not exist, was deleted, or the ID is invalid. ```json theme={null} { "error": "user_not_found", "message": "X user not found" } ``` The target user does not exist or is suspended. ### 424 X API Dependency Failed ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable" } ``` Send `xquik-api-contract: 2026-04-29` to receive this status for dependency failures that return `502` by default. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before retrying the extraction request. ### 502 X API unavailable ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable" } ``` The read service is temporarily unavailable. Retry with exponential backoff. ## Run receipt handoff Treat the `202 Accepted` body as a run receipt, not as extracted data. Store the job ID immediately, then poll or list jobs until the job reaches `completed` or `failed`. ```json theme={null} { "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tool_type": "reply_extractor", "status": "running", "receipt_format": "extraction_job", "poll_path": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "inventory_path": "/api/v1/extractions?status=completed&toolType=reply_extractor", "export_path_after_complete": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=csv" } ``` Store `id`, `toolType`, and `status`. Do not wait for `results`, `totalResults`, `createdAt`, `hasMore`, or `nextCursor` in this response. Use [Get Extraction](/api-reference/extractions/twitter-extraction-results) with the returned `id` to read `job.status`, paginated `results`, `hasMore`, and `nextCursor`. Use [List Extractions](/api-reference/extractions/twitter-scraping-job-history) with `status` and `toolType` filters when a worker needs to resume from job inventory. Use [Export Extraction](/api-reference/extractions/export) after the detail response reports `job.status` as `completed`. **Next steps:** [Get Extraction](/api-reference/extractions/twitter-extraction-results) to retrieve results with pagination, [Export Extraction](/api-reference/extractions/export) to download as CSV/XLSX/Markdown, or [Estimate Extraction](/api-reference/extractions/twitter-scraping-cost-estimator) to check costs before running. # Twitter Scraper Export API & CSV Downloads Source: https://docs.xquik.com/api-reference/extractions/export GET /extractions/{id}/export Export tweet, follower, reply, profile, timeline, community, or list results in 7 formats, with documented CSV, XLSX, JSON, and PDF limits. See costs. ```text theme={null} ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl --fail -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 extraction-reply_extractor.csv ``` ```javascript Node.js theme={null} import { writeFile } from "node:fs/promises"; const extractionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"; const response = await fetch( `https://xquik.com/api/v1/extractions/${extractionId}/export?format=csv`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }, ); if (!response.ok) { throw new Error(`Export failed with ${response.status}`); } const bytes = Buffer.from(await response.arrayBuffer()); await writeFile("extraction-reply_extractor.csv", bytes); ``` ```python Python theme={null} import requests extraction_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" response = requests.get( f"https://xquik.com/api/v1/extractions/{extraction_id}/export", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"format": "csv"}, ) response.raise_for_status() with open("extraction-reply_extractor.csv", "wb") as f: f.write(response.content) ``` ```go Go theme={null} package main import ( "fmt" "io" "net/http" "os" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=csv", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { panic(fmt.Sprintf("export failed with %d", resp.StatusCode)) } file, err := os.Create("extraction-reply_extractor.csv") if err != nil { panic(err) } defer file.Close() if _, err := io.Copy(file, resp.Body); err != nil { panic(err) } } ``` ## File handoff Treat the response body as file bytes. Save it first, then pass the local path to the CRM import, warehouse load, queue, or agent. Do not print downloaded export bytes to shared logs. ```json theme={null} { "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "export_format": "csv", "export_file_path": "extraction-reply_extractor.csv", "content_type": "text/csv; charset=utf-8", "handoff_format": "file" } ``` The response `Content-Disposition` header contains the server filename. Store it when you need an audit trail that connects the downloaded file to the extraction job. ### Format handoff map Use `format=csv` for CRM imports, spreadsheet checks, and follower or reply upserts. Name reply jobs `xquik-replies.csv` and follower jobs `xquik-followers.csv`. Use `format=json` for app ingestion, queue replay, or structured storage. Store the extraction ID and server filename with the local path. Use `format=xlsx` for analyst review and account-management workbooks when humans need filters or formulas. Use `format=md`, `md-document`, `pdf`, or `txt` for human-readable reports. PDF is capped at 10,000 rows. For exports above 100,000 rows, use [Extraction Workflow](/guides/extraction-workflow#durable-json-lines-handoff) to write JSON Lines from paginated `GET /extractions/{id}` results. ## Headers Your API key. Session cookie authentication is also supported. ## Path parameters Extraction job ID returned from [Create Extraction](/api-reference/extractions/create) or [List Extractions](/api-reference/extractions/twitter-scraping-job-history). ## Query parameters Export file format. One of: `csv`, `json`, `md`, `md-document`, `pdf`, `txt`, `xlsx`. ### Export Filters Minimum follower count. Maximum follower count. Minimum following count. Maximum following count. Minimum post count. Maximum post count. Minimum like count. Minimum reply count. Minimum repost count. Minimum view count. Require a non-empty description. Require a non-empty location. Require media. Match verified status. Match a language code. Search exported result text. Include results on or after this date. Include results before this date. ## Export columns File format changes serialization only. The selected columns depend on the extraction tool type. Default exports include 29 columns; `article_extractor` exports 10 article-focused columns. ### Default result columns All extraction tools except `article_extractor` use the default result column set. Some enrichment columns may be empty when the result does not include that data. `User ID`, `Username`, `Display Name`, `Verified`, and `Profile Image`. `Followers`, `Following`, `Posts`, `Media Count`, and `Favorites`. `Description`, `Location`, and `Cover Picture`. `Tweet ID`, `Tweet URL`, `Tweet Text`, and `Tweet Created At`. `Tweet URL` falls back to the status URL when the username is unavailable. `Likes`, `Reposts`, `Replies`, `Quotes`, `Views`, and `Bookmarks`. `Language`, `Source`, and `Conversation ID`. `Article Title`, `Article Preview`, and `Article Body`. ### Article extractor columns `article_extractor` uses a shorter article-focused column set. `Article Title`, `Cover Image`, and `Article Body`. `Author`, `Username`, and `Verified`. `Followers`. `Views`, `Likes`, and `Quotes`. ## Response Returns a file download. The response includes a `Content-Disposition` header with the filename. `format=csv` returns `text/csv; charset=utf-8` with filenames like `extraction-reply_extractor-*.csv`. `format=json` returns `application/json; charset=utf-8` with filenames like `extraction-reply_extractor-*.json`. `format=md` returns `text/markdown; charset=utf-8` with filenames like `extraction-reply_extractor-*.md`. `format=md-document` returns `text/markdown; charset=utf-8` with filenames like `extraction-reply_extractor-*.md`. `format=pdf` returns `application/pdf` with filenames like `extraction-reply_extractor-*.pdf`. `format=txt` returns `text/plain; charset=utf-8` with filenames like `extraction-reply_extractor-*.txt`. `format=xlsx` returns `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` with filenames like `extraction-reply_extractor-*.xlsx`. Results are capped at 100,000 rows (10,000 for PDF). For extractions with more results, the export includes the first 100,000 rows ordered by result ID. ```json theme={null} { "error": "invalid_format", "validFormats": ["csv", "json", "md", "md-document", "pdf", "txt", "xlsx"] } ``` The `format` query parameter is missing or not one of the supported values. ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ```json theme={null} { "error": "not_found" } ``` No extraction job exists with this ID, or it belongs to a different account. ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Wait for the `Retry-After` value before requesting another export. **Next steps:** [Get Extraction](/api-reference/extractions/twitter-extraction-results) to access results via the API with pagination, or [List Extractions](/api-reference/extractions/twitter-scraping-job-history) to find other extraction jobs to export. # Twitter Extraction Status & Result Progress Source: https://docs.xquik.com/api-reference/extractions/twitter-extraction-results GET /extractions/{id} Inspect an extraction job's status, tool, progress, row count, and paginated tweet, follower, reply, profile, timeline, or list results. See examples. ```json theme={null} { "job": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "toolType": "follower_explorer", "status": "completed" }, "results": [], "hasMore": false } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X GET "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890?limit=100" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const extractionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"; const params = new URLSearchParams({ limit: "100" }); const response = await fetch(`https://xquik.com/api/v1/extractions/${extractionId}?${params}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); ``` ```python Python theme={null} import requests extraction_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" response = requests.get( f"https://xquik.com/api/v1/extractions/{extraction_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"limit": 100}, ) data = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890?limit=100", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Path parameters Extraction job ID returned from [Create Extraction](/api-reference/extractions/create) or [List Extractions](/api-reference/extractions/twitter-scraping-job-history). ## Query parameters Number of results to return per page. Default: `100`. Maximum: `1000`. Result ID cursor for pagination. Use the `nextCursor` value from the previous response to fetch the next page. Result fields: `compact`, `full`, or `raw`. Defaults to `full`. Keep enrichment `nested` or merge it with `flat`. Field names: `source`, `camelCase`, or `snake_case`. Deprecated. Use `outputMode=raw`. | Extraction result page column | Response source | Export rule | | ----------------------------- | -------------------------------------------- | --------------------------------------------------------- | | Job ID | `job.id` | Keep this ID with every exported row. | | Extraction tool | `job.toolType` | Preserve the tweet, follower, reply, or profile workflow. | | Job state | `job.status` | Export final rows only after completion. | | Total rows | `job.totalResults` | Compare this count with all saved pages. | | Result ID | `results[].id` | Use this ID for result-page deduplication. | | X user ID | `results[].xUserId` | Preserve the stable profile join. | | Tweet fields | `tweetId`, `tweetText`, and `tweetCreatedAt` | Store them only when returned. | | More pages | `hasMore` | Continue only while this value is `true`. | | Next page | `nextCursor` | Pass this value as the next `cursor` parameter. | ## Response ### 200 OK Extraction job metadata. **Job object fields:** Unique extraction job ID. Tool type used for this extraction. Job status: `completed`, `failed`, or `running`. Total number of extracted results. Target tweet ID. Present for tweet-based tools. Target username. Present for user-based tools. Target X user ID. Present for user-based tools. Target community ID. Present for `community_extractor`, `community_moderator_explorer`, `community_post_extractor`, `community_search`. Search query. Present for `people_search`, `community_search`. Error description. Only present for failed jobs. ISO 8601 timestamp of when the job was created. ISO 8601 timestamp of when the job finished. Only present for completed jobs. Array of extracted user/tweet records for the current page. Only `id` and `xUserId` are guaranteed on every result. All other fields are **omitted entirely** when unavailable (never set to `null`). Always check for the presence of a field before accessing it. **Result object fields:** Unique result ID. X user ID. X username (handle). Omitted if unavailable. X display name. Omitted if unavailable. Follower count at time of extraction. Omitted if unavailable. Whether the user has a verified badge. Omitted if unavailable. URL to the user's profile image. Omitted if unavailable. Tweet ID. Omitted for non-tweet-based extractions. Tweet text content. Omitted for non-tweet-based extractions. ISO 8601 timestamp of the tweet. Omitted for non-tweet-based extractions. ISO 8601 timestamp of when the result was created. Additional profile, tweet, and article metadata. Omitted when unavailable. Contains nested user fields (`description`, `location`, `coverPicture`, `followingCount`, `favouritesCount`, `mediaCount`, `statusesCount`), tweet fields (`likeCount`, `replyCount`, `repostCount`, `quoteCount`, `viewCount`, `bookmarkCount`, `conversationId`, `lang`, `source`), and article fields (`title`, `bodyText`, `previewText`). Enrichment data is returned directly in the API response via the `enrichmentData` field and is also available through [Export Extraction](/api-reference/extractions/export) in CSV, XLSX, or Markdown format with flattened columns. Whether more results exist beyond this page. Cursor for the next `cursor` parameter. Only present when `hasMore` is `true`. ```json theme={null} { "job": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "toolType": "reply_extractor", "status": "completed", "totalResults": 150, "targetTweetId": "1893704267862470862", "createdAt": "2026-02-24T10:00:00.000Z", "completedAt": "2026-02-24T10:00:12.000Z" }, "results": [ { "id": "b2c3d4e5-f6a1-7890-abcd-ef2345678901", "xUserId": "44196397", "xUsername": "elonmusk", "xDisplayName": "Elon Musk", "xFollowersCount": 210500000, "xVerified": true, "xProfileImageUrl": "https://pbs.twimg.com/profile_images/el0n.jpg", "tweetId": "1893710452812718080", "tweetText": "This is a great thread, thanks for sharing.", "tweetCreatedAt": "2026-02-24T10:05:00.000Z", "createdAt": "2026-02-24T10:00:05.000Z" }, { "id": "c3d4e5f6-a1b2-7890-abcd-ef3456789012", "xUserId": "1849726401547751424", "xUsername": "xquik_", "xDisplayName": "Xquik", "xFollowersCount": 2400, "xVerified": false, "xProfileImageUrl": "https://pbs.twimg.com/profile_images/xquik.jpg", "tweetId": "1893712000288563200", "tweetText": "Agreed, very useful tool.", "tweetCreatedAt": "2026-02-24T10:08:30.000Z", "createdAt": "2026-02-24T10:00:06.000Z" } ], "hasMore": true, "nextCursor": "c3d4e5f6-a1b2-7890-abcd-ef3456789012" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found" } ``` No extraction job exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Wait for the `Retry-After` value before polling again. ## Paginating results Results are ordered by ID ascending. To iterate through all results for a large extraction: 1. Make the initial request without the `cursor` parameter. 2. Check `hasMore`. Pass `nextCursor` as the next `cursor` value. 3. Repeat until `hasMore` is `false`. ```javascript theme={null} import { appendFile } from "node:fs/promises"; const extractionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"; let cursor = undefined; let pageIndex = 0; do { const params = new URLSearchParams({ limit: "1000" }); if (cursor) params.set("cursor", cursor); const pageCursor = cursor ?? null; const response = await fetch( `https://xquik.com/api/v1/extractions/${extractionId}?${params}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ); const data = await response.json(); const rows = data.results.map((result) => ({ extraction_id: extractionId, row_id: result.id, x_user_id: result.xUserId, x_username: result.xUsername, tweet_id: result.tweetId, tweet_text: result.tweetText, page_index: pageIndex, page_cursor: pageCursor, next_cursor: data.nextCursor ?? null, has_more: data.hasMore, handoff_format: "jsonl", })); if (rows.length > 0) { await appendFile( "xquik-extraction-results.jsonl", `${rows.map((row) => JSON.stringify(row)).join("\n")}\n`, ); } cursor = data.hasMore ? data.nextCursor : undefined; pageIndex += 1; } while (cursor); ``` ## Cursor handoff Use `GET /extractions/{id}` when an integration needs structured JSON rows, incremental checkpoints, or more rows than a file export can return. Treat `nextCursor` as an opaque checkpoint and pass it back as `cursor`. Store `extraction_id`, `page_index`, `page_cursor`, `next_cursor`, `has_more`, and `result_count` after each successful page. Persist selected fields such as `row_id`, `x_user_id`, `x_username`, `tweet_id`, and `tweet_text`. Do not dump raw result arrays into shared logs. Stream pages to `xquik-extraction-results.jsonl` when you need replayable rows or results beyond the export row cap. Use [Export Extraction](/api-reference/extractions/export) when CSV, JSON, XLSX, Markdown, PDF, or TXT files are enough. Store page checkpoints separately from row data: ```json theme={null} { "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "page_index": 3, "page_cursor": "990100", "next_cursor": "990200", "has_more": true, "result_count": 1000, "handoff_format": "jsonl" } ``` **Next steps:** [Export Extraction](/api-reference/extractions/export) to download all results as CSV, XLSX, or Markdown, or [List Extractions](/api-reference/extractions/twitter-scraping-job-history) to browse your job history. # Twitter Scraping Cost Estimator & Credit Quote Source: https://docs.xquik.com/api-reference/extractions/twitter-scraping-cost-estimator POST /extractions/estimate Estimate API credits before exporting tweet searches, replies, followers, following, profiles, communities, or lists. Plan every extraction before it starts. ```json theme={null} { "estimatedResults": 500, "creditsRequired": "500", "creditsAvailable": "50000", "allowed": true, "source": "replyCount" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} 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", "resultsLimit": 500 }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/extractions/estimate", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ toolType: "reply_extractor", targetTweetId: "1893704267862470862", resultsLimit: 500, }), }); const data = await response.json(); if (data.allowed) { // Safe to proceed with extraction } ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/extractions/estimate", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "toolType": "reply_extractor", "targetTweetId": "1893704267862470862", "resultsLimit": 500, }, ) data = response.json() if data.get("allowed"): # Safe to proceed with extraction pass ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "toolType": "reply_extractor", "targetTweetId": "1893704267862470862", "resultsLimit": 500, }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/extractions/estimate", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Estimate Twitter API Scraping Cost Request a free credit quote before starting any extraction. The response estimates results and required credits. It also checks your available credit balance. A quote never starts a scraping job. Use the same request fields for estimation and extraction. Keep `toolType`, target fields, filters, and `resultsLimit` unchanged. This pairing makes the API cost estimate useful during approval and budgeting. ### Match Each Scraping Task Choose the tool matching the tweets, profiles, or members you need. | Twitter scraping task | `toolType` | Required target | Estimated results | | -------------------------- | -------------------------- | ------------------- | ----------------------------- | | Search tweets with filters | `tweet_search_extractor` | `searchQuery` | Matching tweet search results | | Export tweet replies | `reply_extractor` | `targetTweetId` | Replies beneath one tweet | | Export followers | `follower_explorer` | `targetUsername` | Follower profiles | | Export following | `following_explorer` | `targetUsername` | Followed profiles | | Export profile tweets | `post_extractor` | `targetUsername` | Tweets from one profile | | Export community tweets | `community_post_extractor` | `targetCommunityId` | Tweets from one community | | Export list members | `list_member_extractor` | `targetListId` | Profiles inside one list | | Export list tweets | `list_post_extractor` | `targetListId` | Tweets from list members | Other supported tools estimate likes, media, mentions, quotes, reposts, threads, Spaces, and articles. Use their matching target fields below. ### Control the Credit Quote Set `resultsLimit` when you need a smaller export. The estimator caps `estimatedResults` at that limit. It then adjusts `creditsRequired` for the capped result count. Narrow tweet search estimates with dates, authors, languages, engagement thresholds, or media filters. Reuse those filters when creating the extraction. Changed filters can produce a different estimate. The `source` field explains the result-count signal. Examples include `followers`, `following`, `posts`, `replyCount`, `quoteCount`, `retweetCount`, and `resultsLimit`. Save this field beside the credit quote. Start with a conservative `resultsLimit`. Increase it after the first quote passes your budget. ### Read the Estimate Check these response fields before creating the extraction: * `estimatedResults` projects the matching tweets, replies, profiles, or members. * `creditsRequired` states the projected credit charge. * `creditsAvailable` states the current balance. * `allowed` confirms whether that balance covers the estimate. * `source` identifies the signal behind the projected result count. When `allowed` is `false`, reduce the requested result count. You can also narrow filters or add credits. Request another quote before creating the extraction. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. The estimator accepts the same fields as Create Extraction. ## Body Extraction tool to run or estimate. See the endpoint's tool list. ### Single Targets Tweet ID for a tweet-centered extraction. Username for an account-centered extraction. You may include `@`. Community ID for a community extraction. List ID for a list extraction. Space ID for `space_explorer`. Query for `tweet_search_extractor` or `community_search`. ### Collection Targets Process 1-10,000 Tweet IDs in one collection job. Process 1-100 unique usernames in one collection job. Process 1-100 unique community IDs in one collection job. Process 1-100 unique List IDs in one collection job. Process 1-100 unique search queries in one collection job. Process up to 10,000 mixed targets with automatic routing. Each target accepts a supported string or `{ "kind": "...", "value": "..." }`. Process up to 100 profile relations in one collection job. Each relation target uses `{ "relation": "...", "value": "..." }`. ### Collection Controls Search ranking: `Latest`, `Top`, or `Both`. Defaults to `Latest`. Stop after this many results. Omit it to collect all available results. Maximum results collected for each target. Minimum: `1`. Reply pages collected per target. Range: `1-1,000`. Resume one reply target from this cursor. Merge duplicates across targets. Defaults to `true`. Duplicate handling: `none`, `first`, or `merge`. Use `dedupeMode=merge`. Defaults to `false`. Add matched search terms to collection metadata. Defaults to `false`. Add source target metadata to each result. Defaults to `true`. ### Reply Collection Strategy: `auto`, `complete`, `direct`, `search`, or `thread`. Reply scope: `all`, `direct`, or `nested`. Defaults to `all`. Maximum nested reply depth. Minimum: `1`. Order: `relevance`, `latest`, `oldest`, or `likes`. Exclude replies from the source author. Defaults to `false`. Include the source post. Defaults to `false`. Return only replies with media. Defaults to `false`. Reply start time as ISO 8601 or Unix seconds. Reply end time as ISO 8601 or Unix seconds. ### Tweet Result Filters Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Maximum Tweet repost count. Maximum Tweet reply count. Maximum Tweet quote count. Return only Blue-verified Tweet authors. Defaults to `false`. Match the Tweet card name. Match the source application. Exclude a source application. Match latitude, longitude, and radius. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match a place name. Set the radius for `near`. Match Tweets inside a recent time window. Return only native reposts. Defaults to `false`. Enable safe search. Defaults to `false`. Return only news results. Defaults to `false`. ### Profile Result Filters Minimum profile follower count. Maximum profile follower count. Minimum profile following count. Maximum profile following count. Minimum profile post count. Maximum profile post count. Minimum profile age in days. Match the exact profile verification type. Require a profile website. Defaults to `false`. Require a profile location. Defaults to `false`. Require bio terms separated by commas or lines. Require matching profile location text. Require matching username text. ### Tweet Search Filters These fields apply to `tweet_search_extractor`. Match an author username without `@`. Match replies sent to a username. Match Tweets mentioning a username. Match a language code, such as `en`. Include Tweets on or after `YYYY-MM-DD`. Include Tweets before `YYYY-MM-DD`. Media: `images`, `videos`, `gifs`, `media`, `links`, or `none`. Minimum like count. Minimum repost count. Minimum reply count. Minimum quote count. Return only verified authors. Reply mode: `include`, `exclude`, or `only`. Repost mode: `include`, `exclude`, or `only`. Quote mode: `include`, `exclude`, or `only`. Match one exact phrase. Exclude words or quoted phrases. Match any listed word or quoted phrase. Match hashtags separated by spaces, commas, or lines. Match cashtags separated by spaces, commas, or lines. Match a URL substring or domain. Match a conversation ID. Return only replies to this Tweet ID. Return only quotes of this Tweet ID. Return only reposts of this Tweet ID. Search within a List ID. Search within a place ID. Search within a country code. Set a geographic center and radius. Set a geographic bounding box. Append raw advanced search syntax. ## Response ### 200 OK Whether the extraction can proceed given the current credit balance. Data source used for the estimate. One of: `followers`, `following`, `paginationCap`, `posts`, `quoteCount`, `replyCount`, `resultsLimit`, `retweetCount`, `unknown`. Estimated number of results the extraction will return. Credits this extraction will consume (stringified integer). Credits currently available in your balance (stringified integer). Resolved X user ID when `targetUsername` was provided. Omitted for non-user-based tools. ```json theme={null} { "allowed": true, "creditsRequired": "500", "creditsAvailable": "50000", "estimatedResults": 250, "source": "replyCount", "resolvedXUserId": "123456" } ``` When `allowed` is `false`, credits required exceed the available balance: ```json theme={null} { "allowed": false, "creditsRequired": "250000", "creditsAvailable": "18000", "estimatedResults": 250000, "source": "followers" } ``` ### 400 Invalid input ```json theme={null} { "error": "invalid_input" } ``` Request body is missing or malformed. Ensure `toolType` and the corresponding target field are present. ### 400 Invalid tool type ```json theme={null} { "error": "invalid_tool_type" } ``` The `toolType` value is not one of the 23 supported tools. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 402 Insufficient credits ```json theme={null} { "error": "insufficient_credits" } ``` The available balance cannot cover the estimate. Possible error values include `no_subscription`, `subscription_inactive`, `no_credits`, and `insufficient_credits`. ### 404 Not Found ```json theme={null} { "error": "tweet_not_found", "message": "Tweet not found" } ``` The target tweet does not exist, was deleted, or the ID is invalid. ```json theme={null} { "error": "user_not_found", "message": "X user not found" } ``` The target user does not exist or is suspended. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before requesting another estimate. ## Decision Handoff Treat the `200 OK` response as a planning checkpoint, not a running extraction. Store the estimate with the request you plan to run: ```json theme={null} { "checkpoint_type": "extraction_estimate", "toolType": "reply_extractor", "targetTweetId": "1893704267862470862", "resultsLimit": 500, "estimatedResults": 500, "creditsRequired": "500", "creditsAvailable": "50000", "allowed": true, "source": "replyCount", "next_action": "create_extraction", "create_path": "/api/v1/extractions", "fallback_action": "lower_results_limit_or_add_credits" } ``` When `allowed` is `true`, send the same `toolType`, target fields, filters, and `resultsLimit` to [Create Extraction](/api-reference/extractions/create). Store the returned job ID from that `202 Accepted` receipt. When `allowed` is `false`, lower `resultsLimit`, narrow the target or filters, or add credits before calling [Create Extraction](/api-reference/extractions/create). Store `source` so operators know whether the estimate came from `replyCount`, `followers`, `resultsLimit`, `paginationCap`, or another supported signal. Store `estimatedResults`, `creditsRequired`, `creditsAvailable`, `allowed`, and `source` with the planned extraction request. **Always call this endpoint before running an extraction.** This prevents avoidable credit errors. If `allowed` is `false`, Create Extraction returns a `402` error. Check your current usage on the [dashboard](https://xquik.com/dashboard). **Next steps:** [Create Extraction](/api-reference/extractions/create) to start an extraction, or [Extraction Workflow Guide](/guides/extraction-workflow) for the full flow. # Twitter Scraping Job History & Export Status API Source: https://docs.xquik.com/api-reference/extractions/twitter-scraping-job-history GET /extractions List tweet, follower, following, reply, profile, timeline, media, community, and list extraction jobs by tool, status, and cursor. See request fields. ```json theme={null} { "extractions": [], "hasMore": false } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X GET "https://xquik.com/api/v1/extractions?limit=20&toolType=reply_extractor" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ limit: "20", toolType: "reply_extractor", }); const response = await fetch(`https://xquik.com/api/v1/extractions?${params}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/extractions", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={ "limit": 20, "toolType": "reply_extractor", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/extractions?limit=20&toolType=reply_extractor", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Query parameters Number of extractions to return per page. Default: `50`. Maximum: `100`. Filter by tool type. One of: `article_extractor`, `community_extractor`, `community_moderator_explorer`, `community_post_extractor`, `community_search`, `favoriters`, `follower_explorer`, `following_explorer`, `list_follower_explorer`, `list_member_extractor`, `list_post_extractor`, `mention_extractor`, `people_search`, `post_extractor`, `quote_extractor`, `reply_extractor`, `repost_extractor`, `space_explorer`, `thread_extractor`, `tweet_search_extractor`, `user_likes`, `user_media`, `verified_follower_explorer`. Filter by job status. One of: `completed`, `failed`, `running`. Cursor for pagination. Use the `nextCursor` value from the previous response to fetch the next page. | Scraping job inventory column | Response source | Reconciliation rule | | ----------------------------- | ---------------------------- | ----------------------------------------------------- | | Job ID | `extractions[].id` | Open the matching extraction result pages. | | Scraper tool | `extractions[].toolType` | Separate tweet, follower, reply, and profile jobs. | | Job state | `extractions[].status` | Route completed, failed, and running jobs separately. | | Extracted rows | `extractions[].totalResults` | Compare this count with saved result pages. | | Started at | `extractions[].createdAt` | Preserve the collection start time. | | Completed at | `extractions[].completedAt` | Measure completed-job duration. | | More jobs | `hasMore` | Continue only while this value is `true`. | | Next page | `nextCursor` | Pass this value as the next `cursor` parameter. | ## Response ### 200 OK Array of extraction job summaries. **Extraction object fields:** Unique extraction job ID. Tool type used for this extraction. Job status: `completed`, `failed`, or `running`. Total number of extracted results. ISO 8601 timestamp of when the job was created. ISO 8601 timestamp of when the job finished. Only present for completed jobs. Whether more results exist beyond this page. Cursor for the next `cursor` parameter. Only present when `hasMore` is `true`. ```json theme={null} { "extractions": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "toolType": "reply_extractor", "status": "completed", "totalResults": 150, "createdAt": "2026-02-24T10:00:00.000Z", "completedAt": "2026-02-24T10:00:12.000Z" }, { "id": "b2c3d4e5-f6a1-7890-abcd-ef2345678901", "toolType": "follower_explorer", "status": "completed", "totalResults": 4832, "createdAt": "2026-02-23T18:30:00.000Z", "completedAt": "2026-02-23T18:31:45.000Z" }, { "id": "c3d4e5f6-a1b2-7890-abcd-ef3456789012", "toolType": "repost_extractor", "status": "failed", "totalResults": 0, "createdAt": "2026-02-23T12:00:00.000Z" } ], "hasMore": true, "nextCursor": "eyJjIjoiMjAyNi0wMi0yM1QxMjowMDowMC4wMDBaIiwiaSI6Ijc3NzY4In0" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Wait for the `Retry-After` value before making another request. ## Pagination Results are ordered by creation date (newest first). To iterate through all extractions: 1. Make the initial request without the `cursor` parameter. 2. Check `hasMore`. Pass `nextCursor` as the next `cursor` value. 3. Repeat until `hasMore` is `false`. ```javascript theme={null} import { appendFile } from "node:fs/promises"; let cursor = undefined; let pageIndex = 0; do { const params = new URLSearchParams({ limit: "100", status: "completed", }); if (cursor) params.set("cursor", cursor); const pageCursor = cursor ?? null; const response = await fetch(`https://xquik.com/api/v1/extractions?${params}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const data = await response.json(); const rows = data.extractions.map((job) => ({ extraction_id: job.id, tool_type: job.toolType, status: job.status, total_results: job.totalResults, created_at: job.createdAt, completed_at: job.completedAt ?? null, detail_path: `/api/v1/extractions/${job.id}`, export_path: job.status === "completed" ? `/api/v1/extractions/${job.id}/export?format=csv` : null, page_index: pageIndex, page_cursor: pageCursor, next_cursor: data.nextCursor ?? null, has_more: data.hasMore, handoff_format: "jsonl", })); if (rows.length > 0) { await appendFile( "xquik-extraction-jobs.jsonl", `${rows.map((row) => JSON.stringify(row)).join("\n")}\n`, ); } cursor = data.hasMore ? data.nextCursor : undefined; pageIndex += 1; } while (cursor); ``` ## Job inventory handoff Use `GET /extractions` as the job inventory step before fetching details or exporting files. Treat `nextCursor` as opaque and pass it back as `cursor`; do not dump raw job lists into shared logs. Store `page_index`, `page_cursor`, `next_cursor`, `has_more`, `limit`, `tool_type_filter`, and `status_filter` after each successful page. Store completed job `id`, `toolType`, `totalResults`, `completedAt`, `detail_path`, and `export_path` for the next fetch or file export. Keep `status`, `createdAt`, and `id` so dashboards can retry failed jobs or poll running jobs without rereading older pages. Use [Get Extraction](/api-reference/extractions/twitter-extraction-results) for paginated JSON rows, or [Export Extraction](/api-reference/extractions/export) for files. Store page checkpoints separately from job rows: ```json theme={null} { "page_index": 2, "page_cursor": "eyJjIjoiMjAyNi0wMi0yM1QxODozMTo0NS4wMDBaIiwiaSI6Ijc3NzY4In0", "next_cursor": "eyJjIjoiMjAyNi0wMi0yM1QxMjowMDowMC4wMDBaIiwiaSI6Ijc3NzY0In0", "has_more": true, "limit": 100, "tool_type_filter": "reply_extractor", "status_filter": "completed", "handoff_format": "jsonl" } ``` **Next steps:** [Get Extraction](/api-reference/extractions/twitter-extraction-results) to retrieve full results for a specific job, or [Create Extraction](/api-reference/extractions/create) to run a new extraction. # Create Guest Wallet for Pay-per-request X API Source: https://docs.xquik.com/api-reference/guest-wallets/create POST /guest-wallets Create a USD 10-250 hosted checkout and accountless API key for tweet, profile, follower, reply, timeline, community, and list reads. See safe examples. ```json theme={null} { "account_required": false, "amount": { "amount_minor": 1000, "currency": "usd" }, "api_key": "xq_example_returned_once", "authorization": { "header": "Authorization", "scheme": "Bearer" }, "checkout_url": "https://checkout.example/guest/example", "credential_notice": "Store api_key and the Idempotency-Key securely before sharing checkout_url. No email recovery is available.", "credits": "66666", "expires_at": "2026-07-13T13:00:00.000Z", "instructions": "Give checkout_url to the user. They must complete payment on the hosted checkout page. Never submit payment for them. After payment, poll status_url every poll_after_seconds until latest_purchase.status is no longer pending.", "poll_after_seconds": 2 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Reuse this Idempotency-Key only with the original request." } ``` ```json theme={null} { "error": "checkout_unavailable", "message": "Checkout unavailable. Retry with a new Idempotency-Key." } ``` ```json theme={null} { "error": "body_too_large", "message": "Request body is too large." } ``` ```json theme={null} { "error": "unsupported_media_type", "message": "Send Content-Type: application/json." } ``` ```json theme={null} { "error": "guest_wallet_unavailable", "message": "Guest wallet unavailable. Create a new wallet or contact support." } ``` ```json theme={null} { "error": "rate_limited", "message": "Try again later." } ``` ```json theme={null} { "error": "guest_wallets_unavailable", "message": "Guest wallet checkout is temporarily unavailable." } ```
For the complete documentation index, see llms.txt.
Create a prepaid read wallet without an account, email address, or dashboard. This endpoint creates a one-use hosted checkout after the user confirms a USD amount. It does not charge the user. Ask the user to confirm the amount before calling this endpoint. Give the returned `checkout_url` to the user. Never open, submit, or complete the payment for them. ```bash cURL theme={null} idempotency_key=$(uuidgen | tr '[:upper:]' '[:lower:]') response=$(curl -sS -X POST https://xquik.com/api/v1/guest-wallets \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $idempotency_key" \ -d '{"amount_minor": 1000, "currency": "usd"}') api_key=$(jq -r '.api_key' <<<"$response") checkout_url=$(jq -r '.checkout_url' <<<"$response") wallet_id=$(jq -r '.wallet_id' <<<"$response") # Store $api_key and $idempotency_key in your secret manager. Do not print them. printf 'Created guest wallet %s. Open %s\n' "$wallet_id" "$checkout_url" ``` ```javascript Node.js theme={null} import { randomUUID } from "node:crypto"; const idempotencyKey = randomUUID(); const response = await fetch("https://xquik.com/api/v1/guest-wallets", { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": idempotencyKey, }, body: JSON.stringify({ amount_minor: 1000, currency: "usd" }), }); const wallet = await response.json(); const apiKey = wallet.api_key; // Store apiKey and idempotencyKey in your secret manager. Do not print them. process.stdout.write(String(wallet.checkout_url) + "\n"); ``` Store both `api_key` and the original `Idempotency-Key` as secrets. An exact replay can return the same response, including the key. No email recovery is available. Give only `checkout_url` to the user. After payment, poll `status_url` every `poll_after_seconds` with the guest key. Stop when `latest_purchase.status` is no longer `pending`. Do not call paid reads until `usable` is `true`. ## Headers Must be `application/json`. A cryptographically random UUID v4. Reuse it only for an exact retry of the same amount. Store it as a secret because it can recover the initial key. ## Body Confirmed USD amount in cents. Minimum `1000` and maximum `25000`. Must be `usd`. ## Response Always `false`. Confirmed amount in minor units and `usd` currency. One-use hosted checkout URL for the user to open. Guest key returned on initial creation and exact idempotent replay. Store it as a secret. Required Bearer header and scheme. One-time credential storage guidance. API URL to poll with the guest key. Minimum polling delay. Always `2` for a pending purchase. Always `true`. Required user interaction and polling guidance. Guest purchase ID. Initial purchase status. Normally `pending`. Pending checkout expiry. New checkouts expire after 60 minutes. Credits to grant after verified payment. Guest wallet ID. ```json theme={null} { "account_required": false, "amount": { "amount_minor": 1000, "currency": "usd" }, "api_key": "xq_example_returned_once", "authorization": { "header": "Authorization", "scheme": "Bearer" }, "checkout_url": "https://checkout.example/guest/example", "credential_notice": "Store api_key and the Idempotency-Key securely before sharing checkout_url. No email recovery is available.", "credits": "66666", "expires_at": "2026-07-13T13:00:00.000Z", "instructions": "Give checkout_url to the user. They must complete payment on the hosted checkout page. Never submit payment for them. After payment, poll status_url every poll_after_seconds until latest_purchase.status is no longer pending.", "poll_after_seconds": 2, "purchase_id": "gp_example", "requires_user_interaction": true, "status": "pending", "status_url": "https://xquik.com/api/v1/guest-wallets/status", "wallet_id": "gw_example" } ``` Check the UUID v4 header, amount, currency, JSON, and request fields. The same `Idempotency-Key` was used with a different amount or request. Generate a new UUID v4 after the user confirms the new request. The pending checkout expired or can no longer be used. Ask the user to confirm before creating a new wallet. Reduce the request body, then retry with the same `Idempotency-Key`. Send `Content-Type: application/json`. The wallet is unavailable. Do not retry payment automatically. Wait for `Retry-After` before retrying the same request. Checkout is temporarily unavailable. Retry with the same `Idempotency-Key`. The response sends `Cache-Control: no-store, private`. An exact replay also sends `Idempotent-Replayed: true`. **Related:** [Guest wallet guide](/guides/guest-wallets) · [Get guest wallet status](/api-reference/guest-wallets/status) · [Top up guest wallet](/api-reference/guest-wallets/topup) # Guest Wallet API Status & Payment Activation Source: https://docs.xquik.com/api-reference/guest-wallets/status GET /guest-wallets/status Check a guest API key's activation, credit balance, hosted payment status, and access to tweet, profile, follower, and reply reads. See request fields. ```json theme={null} { "balance": "66666", "latest_purchase": { "amount": { "amount_minor": 1000, "currency": "usd" }, "checkout_url": null, "credits": "66666", "expires_at": "2026-07-13T13:00:00.000Z", "purchase_id": "gp_example" }, "poll_after_seconds": null, "scope": "paid_reads", "status": "active", "top_up": { "method": "POST", "path": "/api/v1/guest-wallets/topups" }, "usable": true, "wallet_id": "gw_example" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limited", "message": "Try again later." } ```
For the complete documentation index, see llms.txt.
Poll this endpoint after the user completes hosted checkout. The guest key can authenticate this status route while the wallet is pending, but it cannot call paid read routes until payment is verified. ```bash cURL theme={null} curl https://xquik.com/api/v1/guest-wallets/status \ -H "Authorization: Bearer xq_your_guest_key_here" | jq ``` Wait at least `poll_after_seconds` before polling again. Continue while it is non-null, then stop. Use `usable` to decide whether paid reads can run. An active wallet can remain usable while a top-up is pending. ## Headers Send the guest key as `Bearer xq_your_guest_key_here`. ## Response Available guest wallet credits. Always `paid_reads`. Combined wallet and pending-checkout state: `active`, `pending`, `expired`, `failed`, `frozen`, or `closed`. A pending top-up can coexist with `usable: true`. Whether the key can call eligible paid read routes. `2` while payment is pending. Otherwise `null`. Latest amount, credits, expiry, purchase ID, checkout URL while pending, and purchase status. Direct REST top-up action when the wallet is usable and has no pending checkout. Guest wallet ID. ## Interpret the result Use these fields together: | Condition | Meaning | Action | | ---------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------ | | `usable: true`, `status: active` | Paid reads can run. No checkout is pending. | Call eligible reads. Use `top_up` only after confirmation. | | `usable: true`, `status: pending` | Existing credits remain usable while a top-up is pending. | Continue affordable reads. Poll after the stated delay. | | `usable: false`, `status: pending` | The initial checkout still awaits verified payment. | Do not call paid reads. Poll after the stated delay. | | `usable: false`, `status: expired` or `failed` | The initial checkout reached a terminal state. | Stop polling. Get new confirmation before creating another wallet. | | `usable: false`, `status: frozen` or `closed` | Wallet access is unavailable. | Stop paid reads. Do not retry payment automatically. | `latest_purchase.status` describes only the latest purchase. Use the top-level `usable` field for paid-read access. ```json theme={null} { "balance": "66666", "latest_purchase": { "amount": { "amount_minor": 1000, "currency": "usd" }, "checkout_url": null, "credits": "66666", "expires_at": "2026-07-13T13:00:00.000Z", "purchase_id": "gp_example", "status": "paid" }, "poll_after_seconds": null, "scope": "paid_reads", "status": "active", "top_up": { "method": "POST", "path": "/api/v1/guest-wallets/topups" }, "usable": true, "wallet_id": "gw_example" } ``` The guest key is missing or invalid. No email or account recovery is available. Wait for `Retry-After` before polling again. Use `usable` to decide whether the key can call paid reads. The response sends `Cache-Control: no-store, private` and never returns the guest key. **Related:** [Guest wallet guide](/guides/guest-wallets) · [Create guest wallet](/api-reference/guest-wallets/create) · [Top up guest wallet](/api-reference/guest-wallets/topup) # Top Up Guest Wallet Credits for X API Reads Source: https://docs.xquik.com/api-reference/guest-wallets/topup POST /guest-wallets/topups Create a USD 10-250 hosted checkout to add tweet, profile, follower, reply, timeline, community, and list read credits to a guest key. See response fields. ```json theme={null} { "account_required": false, "amount": { "amount_minor": 1000, "currency": "usd" }, "checkout_url": "https://checkout.example/guest/example", "credits": "66666", "expires_at": "2026-07-13T13:00:00.000Z", "instructions": "Give checkout_url to the user. They must complete payment on the hosted checkout page. Never submit payment for them. After payment, poll status_url every poll_after_seconds until latest_purchase.status is no longer pending.", "poll_after_seconds": 2, "purchase_id": "gp_example", "requires_user_interaction": true, "status": "pending" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Reuse this Idempotency-Key only with the original request." } ``` ```json theme={null} { "error": "checkout_unavailable", "message": "Checkout unavailable. Retry with a new Idempotency-Key." } ``` ```json theme={null} { "error": "body_too_large", "message": "Request body is too large." } ``` ```json theme={null} { "error": "unsupported_media_type", "message": "Send Content-Type: application/json." } ``` ```json theme={null} { "error": "guest_wallet_unavailable", "message": "Guest wallet unavailable. Create a new wallet or contact support." } ``` ```json theme={null} { "error": "rate_limited", "message": "Try again later." } ``` ```json theme={null} { "error": "guest_wallets_unavailable", "message": "Guest wallet checkout is temporarily unavailable." } ```
For the complete documentation index, see llms.txt.
Add credits to an existing guest wallet. The wallet keeps the same `paid_reads` key. This endpoint creates a one-use hosted checkout only after the user confirms the amount. It does not charge the user. Never call this endpoint automatically after a `402`. Show the available option and amount, then wait for explicit user confirmation. ```bash cURL theme={null} idempotency_key=$(uuidgen | tr '[:upper:]' '[:lower:]') curl -X POST https://xquik.com/api/v1/guest-wallets/topups \ -H "Authorization: Bearer xq_your_guest_key_here" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $idempotency_key" \ -d '{"amount_minor": 2500, "currency": "usd"}' | jq ``` Keep the current guest key and store the new `Idempotency-Key` as a secret. Give only `checkout_url` to the user. After payment, poll `status_url` every `poll_after_seconds` with the same key. Stop when `latest_purchase.status` is no longer `pending`. Use `usable` to decide whether paid reads can run. ## Headers Send the guest key as `Bearer xq_your_guest_key_here`. Must be `application/json`. A new cryptographically random UUID v4. Reuse it only for an exact retry of this top-up request. ## Body Confirmed USD amount in cents. Minimum `1000` and maximum `25000`. Must be `usd`. ## Response Always `false`. Confirmed amount in minor units and `usd` currency. One-use hosted checkout URL for the user to open. Credits to grant after verified payment. Pending checkout expiry. Required user interaction and polling guidance. Guest purchase ID. Guest wallet status URL. Minimum polling delay. Always `2` while pending. Always `true`. Initial top-up status. Normally `pending`. Existing guest wallet ID. ```json theme={null} { "account_required": false, "amount": { "amount_minor": 2500, "currency": "usd" }, "checkout_url": "https://checkout.example/guest/example", "credits": "166666", "expires_at": "2026-07-13T13:00:00.000Z", "instructions": "Give checkout_url to the user. They must complete payment on the hosted checkout page. Never submit payment for them. After payment, poll status_url every poll_after_seconds until latest_purchase.status is no longer pending.", "poll_after_seconds": 2, "purchase_id": "gp_example", "requires_user_interaction": true, "status": "pending", "status_url": "https://xquik.com/api/v1/guest-wallets/status", "wallet_id": "gw_example" } ``` This response never returns a new API key. Check the UUID v4 header, amount, currency, JSON, and request fields. The guest key is missing or invalid. The same `Idempotency-Key` was used for a different request. The checkout expired or can no longer be used. Reduce the request body, then retry with the same `Idempotency-Key`. Send `Content-Type: application/json`. The wallet is unavailable. Check guest wallet status before taking another action. Wait for `Retry-After` before retrying the same request. Checkout is temporarily unavailable. Retry with the same `Idempotency-Key`. The response sends `Cache-Control: no-store, private`. An exact replay also sends `Idempotent-Replayed: true`. **Related:** [Guest wallet guide](/guides/guest-wallets) · [Create guest wallet](/api-reference/guest-wallets/create) · [Get guest wallet status](/api-reference/guest-wallets/status) # Twitter Account Monitor API & Real-Time Webhooks Source: https://docs.xquik.com/api-reference/monitors/create POST /monitors Monitor one X account every second. Track tweets, replies, quotes, reposts, mentions, media, links, and profile changes. Deliver signed webhook alerts. ```json theme={null} { "id": "42", "username": "elonmusk", "xUserId": "1234567890", "eventTypes": [ "tweet.new" ], "isActive": true, "createdAt": "2025-01-15T12:00:00Z", "nextBillingAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "user_not_found", "message": "X user not found. Check the username." } ``` ```json theme={null} { "error": "monitor_already_exists", "message": "Monitor already exists." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
Create a Twitter account monitor for one known profile. Select tweet, profile, or availability events. Store the monitor ID. Then connect signed webhooks. **Requires 22 available credits.** The username lookup costs 1 credit. The first active monitor hour costs 21 credits. Monitors are unlimited. Active monitors check every 1 second. Webhook and event deliveries are included in active monitor billing. ## Create a Twitter Monitor for One Account Use this Twitter monitor API for continuous checks on one known profile. Send the username without `@`. Choose only the required event types. This API endpoint gives you one stable monitor ID. Use it to join events and webhook deliveries. Use `POST /monitors` for Twitter API monitoring from one username. This tweet monitor covers selected posts, replies, quotes, reposts, and profile changes. It does not search posts from every public account. ## Choose the Right Twitter Monitoring Surface Choose one surface from the required alert scope. | Monitoring need | Xquik surface | Result | | ----------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------- | | Continuous changes from one known account | `POST /monitors` | Selected tweet and profile events from that username. | | Specific keywords across many accounts | [Create Keyword Monitor](/api-reference/monitors/create-keyword) | Matching tweet events for one stored query. | | Historical or on-demand tweet results | [Search Tweets](/api-reference/x/search-tweets) | One paginated result set without continuous alerts. | Do not replace one surface with another after collection starts. Store the chosen monitor type beside every downstream alert. ## How Do I Monitor a Twitter Account With an API? Create one active monitor for each username requiring continuous checks. Send the username without `@` and choose exact event types. Xquik resolves the username to one stable X user ID. Store both the monitor ID and resolved user ID. Teams can monitor Twitter account activity without maintaining a stream connection. The account monitor checks selected changes every second. Real-time Twitter alerts start after connecting a signed webhook. Stored events remain available for later inspection through the Events API. Use the monitor ID for updates, pauses, deletion, and event joins. Use the X user ID for stable account joins. Never use a display name as the account key. ## Which Twitter Account Activity Can Trigger Alerts? Select `tweet.new` for original posts from the monitored account. Add `tweet.reply`, `tweet.quote`, or `tweet.retweet` for conversation activity. Use format events for media, links, polls, mentions, hashtags, and long posts. Each selected event type creates a focused alert stream. Profile events cover names, usernames, bios, locations, URLs, avatars, and banners. They also cover verification, protection, pinned posts, and account availability. Use `profile.unavailable.changed` when account availability affects a workflow. Keep previous and current profile values with the stored event. Choose only events that trigger a real downstream action. Extra event types create alerts that workers must still review. Update the monitor when the required event set changes. Do not infer unselected changes from tweet or profile counts. ## How Do Real-Time Twitter Alerts Reach My Application? Create the account monitor before registering its delivery endpoint. Then create an HTTPS webhook with the required event filters. Save the one-time webhook secret in a secret manager. Send a signed test before accepting production deliveries. Verify `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` first. Compute the signature from the raw request body. Reject invalid signatures before parsing or queuing the payload. See [Webhook Verification](/webhooks/verification) for complete receiver examples. Use `deliveryId` for delivery-level idempotency. Use `streamEventId` for one-time monitor event processing. Store the queue row before returning a successful receiver response. Inspect [Webhook Deliveries](/api-reference/webhooks/deliveries) when alerts stop arriving. ## How Do I Monitor Competitor Twitter Account Activity? Create one account monitor for each competitor username. Track new tweets, replies, quotes, reposts, links, and media. Add profile events for bio, username, avatar, banner, and pinned-post changes. Store each event type, timestamp, monitor ID, and X user ID. This workflow records concrete account changes. It does not calculate sentiment, brand reputation, or share of voice. Use a keyword monitor for mentions across unrelated accounts. Use tweet search for a retrospective competitor query. Keep competitor alerts separate from your own account alerts. Route each monitor ID to its intended queue or workspace. That separation prevents one competitor event from triggering unrelated jobs. ## How Do I Monitor Multiple Twitter Accounts? Create one monitor request per username. Monitors are unlimited, but each active monitor has hourly billing. Keep at least 22 credits before creating or restoring each monitor. Store every returned monitor ID before creating the next request. An active duplicate returns `409 monitor_already_exists`. Use [List Monitors](/api-reference/monitors/list) to recover its stored ID. Use [Update Monitor](/api-reference/monitors/update) to change events or active state. Do not create replacement monitors when a pause is sufficient. Store each resolved X user ID beside the current username. This join remains stable when a username-change event arrives. Review `nextBillingAt` before keeping large monitor sets active. ## What Does an Account Monitor Not Track? Account monitors do not emit follower-gained or follower-lost events. They also exclude likes, bookmarks, and direct-message activity. They do not return a complete historical timeline. They do not search specific keywords across every account. Use [Followers](/api-reference/x/followers) for paginated follower snapshots. Use [Following](/api-reference/x/following) for paginated following snapshots. Use [User Tweets](/api-reference/x/user-tweets) for timeline retrieval. Use [Create Keyword Monitor](/api-reference/monitors/create-keyword) for matching queries. Never substitute aggregate counts for missing participant or relationship records. Choose the endpoint that returns the required tweets, profiles, or relationships. ## How Do I Recover From Monitor and Webhook Failures? List account monitors before repeating an uncertain create request. Reuse the stored monitor when the first request succeeded. Retry the same username and event types only when no monitor exists. Correct invalid usernames or event arrays after a `400` response. Replace missing credentials after `401` authentication errors. Add credits before retrying a `402 insufficient_credits` response. Check the username after a `404 user_not_found` response. Reuse the existing monitor after a `409` duplicate response. Respect `Retry-After` before repeating a `429` request. Webhook failures do not require replacing a healthy monitor. Fix the receiver, verify its signature code, and send another signed test. Then inspect delivery attempts and join `streamEventId` to the stored event. ```bash cURL theme={null} curl --fail-with-body -X POST https://xquik.com/api/v1/monitors \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "username": "elonmusk", "eventTypes": ["tweet.new", "tweet.reply"] }' | jq -c '{ monitor_id: .id, username: .username, x_user_id: .xUserId, event_types: .eventTypes, is_active: .isActive, created_at: .createdAt, next_billing_at: .nextBillingAt, verify_endpoint: "/api/v1/monitors/\(.id)", update_endpoint: "/api/v1/monitors/\(.id)", delete_endpoint: "/api/v1/monitors/\(.id)", events_endpoint: "/api/v1/events?monitorId=\(.id)", event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/monitors", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ username: "elonmusk", eventTypes: ["tweet.new", "tweet.reply"], }), }); const monitor = await response.json(); if (!response.ok) { throw new Error(monitor.message || "Twitter monitor creation failed."); } const monitorState = { monitor_id: monitor.id, username: monitor.username, x_user_id: monitor.xUserId, event_types: monitor.eventTypes, is_active: monitor.isActive, created_at: monitor.createdAt, next_billing_at: monitor.nextBillingAt, verify_endpoint: `/api/v1/monitors/${monitor.id}`, update_endpoint: `/api/v1/monitors/${monitor.id}`, delete_endpoint: `/api/v1/monitors/${monitor.id}`, events_endpoint: `/api/v1/events?monitorId=${monitor.id}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(monitorState)}\n`); ``` ```python Python theme={null} import json import requests response = requests.post( "https://xquik.com/api/v1/monitors", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "username": "elonmusk", "eventTypes": ["tweet.new", "tweet.reply"], }, ) monitor = response.json() if not response.ok: raise RuntimeError(monitor.get("message", "Twitter monitor creation failed.")) monitor_state = { "monitor_id": monitor["id"], "username": monitor["username"], "x_user_id": monitor["xUserId"], "event_types": monitor["eventTypes"], "is_active": monitor["isActive"], "created_at": monitor["createdAt"], "next_billing_at": monitor["nextBillingAt"], "verify_endpoint": f"/api/v1/monitors/{monitor['id']}", "update_endpoint": f"/api/v1/monitors/{monitor['id']}", "delete_endpoint": f"/api/v1/monitors/{monitor['id']}", "events_endpoint": f"/api/v1/events?monitorId={monitor['id']}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(monitor_state)) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "io" "log" "net/http" "os" ) type Monitor struct { ID string `json:"id"` Username string `json:"username"` XUserID string `json:"xUserId"` EventTypes []string `json:"eventTypes"` IsActive bool `json:"isActive"` CreatedAt string `json:"createdAt"` NextBillingAt string `json:"nextBillingAt"` } type MonitorState struct { CreatedAt string `json:"created_at"` DeleteEndpoint string `json:"delete_endpoint"` DeliveriesEndpointPattern string `json:"deliveries_endpoint_pattern"` EventDetailEndpointPattern string `json:"event_detail_endpoint_pattern"` EventsEndpoint string `json:"events_endpoint"` EventTypes []string `json:"event_types"` IsActive bool `json:"is_active"` MonitorID string `json:"monitor_id"` NextBillingAt string `json:"next_billing_at"` UpdateEndpoint string `json:"update_endpoint"` Username string `json:"username"` VerifyEndpoint string `json:"verify_endpoint"` WebhooksEndpoint string `json:"webhooks_endpoint"` XUserID string `json:"x_user_id"` } func main() { body, _ := json.Marshal(map[string]interface{}{ "username": "elonmusk", "eventTypes": []string{"tweet.new", "tweet.reply"}, }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/monitors", bytes.NewReader(body)) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { problem, readErr := io.ReadAll(resp.Body) if readErr != nil { log.Fatal(readErr) } log.Fatalf("Twitter monitor creation failed with %d: %s", resp.StatusCode, string(problem)) } var monitor Monitor if err := json.NewDecoder(resp.Body).Decode(&monitor); err != nil { log.Fatal(err) } state := MonitorState{ CreatedAt: monitor.CreatedAt, DeleteEndpoint: "/api/v1/monitors/" + monitor.ID, DeliveriesEndpointPattern: "/api/v1/webhooks/{webhook_id}/deliveries", EventDetailEndpointPattern: "/api/v1/events/{event_id}", EventsEndpoint: "/api/v1/events?monitorId=" + monitor.ID, EventTypes: monitor.EventTypes, IsActive: monitor.IsActive, MonitorID: monitor.ID, NextBillingAt: monitor.NextBillingAt, UpdateEndpoint: "/api/v1/monitors/" + monitor.ID, Username: monitor.Username, VerifyEndpoint: "/api/v1/monitors/" + monitor.ID, WebhooksEndpoint: "/api/v1/webhooks", XUserID: monitor.XUserID, } if err := json.NewEncoder(os.Stdout).Encode(state); err != nil { log.Fatal(err) } } ``` Each code example maps the response to one monitor row. Save the account IDs, event filter, active state, next charge time, and monitor routes. The routes cover updates, events, webhooks, and delivery checks. ## Account Monitor Handoff Use `POST /monitors` for one X account. Send alerts to a queue, CRM, warehouse, Slack, or an agent. It checks selected tweets and profile changes every second. Create the monitor first. Then create a signed webhook with [`POST /webhooks`](/api-reference/webhooks/create). Call [`POST /webhooks/{id}/test`](/api-reference/webhooks/test) before enabling production alerts. | Created monitor column | Response source | Setup rule | | ---------------------- | --------------- | --------------------------------------------------- | | Monitor ID | `id` | Store this ID before configuring downstream alerts. | | X username | `username` | Store the normalized username without `@`. | | X user ID | `xUserId` | Use this stable ID for account joins. | | Event filter | `eventTypes` | Align webhook subscriptions with these event types. | | Polling state | `isActive` | Route only active monitors into alert checks. | | Creation time | `createdAt` | Store this timestamp with each monitor audit. | | Billing checkpoint | `nextBillingAt` | Review available credits before this time. | Store `id` as `monitor_id`. Verify state with [Get Monitor](/api-reference/monitors/twitter-account-monitor-status). Pause or resume with [Update Monitor](/api-reference/monitors/update). Call [Delete Monitor](/api-reference/monitors/delete-twitter-account-monitor) only when tracking should stop permanently. Store `username` after trimming the `@` prefix. Store `xUserId` for stable joins, dedupe, and downstream account mapping. Store `eventTypes`; keep [List Webhooks](/api-reference/webhooks/list) subscriptions aligned so expected account activity delivers. Read `isActive` and `nextBillingAt` before enabling alerts or estimating hourly monitor burn. Read `monitorType: "account"`, `monitorId`, and `username` from [List Events](/api-reference/events/list). Use them to join stored events to the account. Use [Get Event](/api-reference/events/get) for one event. Use `deliveryId` for receiver idempotency and [List Deliveries](/api-reference/webhooks/deliveries) for delivery attempts. Join `streamEventId` to event IDs; do not use `x_event_id` as the delivery join key. Store `eventType`, `occurredAt`, and `data` with the downstream job. ### What Should a Webhook Receiver Save? Save the monitor ID, username, user ID, event type, event time, and delivery ID. Use the monitor ID to group alerts for one account. Use the delivery ID to stop the same job twice. Keep the event ID for later checks. These fields help with replay, audits, retries, and clear alert ownership. Active account monitors check every 1 second. Each active hour costs 21 credits. You need 22 available credits to create or restore a monitor. That total includes a 1-credit username lookup and the first active hour. Pause the monitor through [Update Monitor](/api-reference/monitors/update) (`PATCH /monitors/{id}`). Set `{ "isActive": false }` when alerts should stop. ## Headers Send your Xquik API key. Create one in the [Xquik dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Must be `application/json`. ## Body Send an X username with 1-15 letters, numerals, or underscores. Xquik removes a leading `@` before validation. Array of event types to subscribe to. At least 1 required. See [Valid Event Types](#valid-event-types) below. ## Valid Event Types Choose only the exact event types in the goal map below. ### Match Event Types to Monitor Goals | Monitor goal | Event types | Stored signal | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | New posts and conversations | `tweet.new`, `tweet.reply`, `tweet.quote`, `tweet.retweet` | Original posts, replies, quotes, or reposts from one profile. | | Tweet content formats | `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, `tweet.longform` | Posts containing the selected format or reference. | | Profile text changes | `profile.name.changed`, `profile.username.changed`, `profile.bio.changed`, `profile.location.changed`, `profile.url.changed` | Previous and current profile text values. | | Profile image changes | `profile.avatar.changed`, `profile.banner.changed` | Old and new avatar or banner references. | | Profile state changes | `profile.verified.changed`, `profile.protected.changed`, `profile.pinned_tweet.changed`, `profile.unavailable.changed` | Verification, visibility, pinned post, or availability changes. | Account monitors do not emit follower-gained or follower-lost events. Use [Followers](/api-reference/x/followers) and [Following](/api-reference/x/following) for paginated relationship snapshots. Original tweet from the monitored account. Used when no reply, quote, or retweet signal is present. Quote tweet from the monitored account. Include this when quote activity should create stored events and webhook deliveries. Reply from the monitored account. Include this when support routing, conversation tracking, or alerting needs replies. Retweet from the monitored account. Include this when repost activity should create stored events and webhook deliveries. ## Response ### 201 Created Unique monitor ID. Stored X username after trimming and removing the `@` prefix. Resolved X user ID for the account. Event types this monitor is subscribed to. Whether the monitor is currently active. Xquik records the creation time in ISO 8601 format. Next hourly credit charge time. New active monitors are due immediately. ```json theme={null} { "id": "7", "username": "elonmusk", "xUserId": "44196397", "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T10:30:00.000Z" } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` Invalid username format or missing/invalid `eventTypes` array. ### 401 Missing API Key ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Missing or invalid API key or OAuth bearer token. ### 402 Payment Required ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` Keep at least 22 credits available before sending this request. The API may return `insufficient_credits` when the balance is too low. ### 404 User Not Found ```json theme={null} { "error": "user_not_found", "message": "X user not found. Check the username." } ``` Xquik could not find that username. Check the spelling. ### 409 Duplicate ```json theme={null} { "error": "monitor_already_exists", "message": "Monitor already exists." } ``` An active monitor already tracks this X account. Use [Update Monitor](/api-reference/monitors/update) to change event types instead. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. A previously deleted monitor is reactivated with new event types. An active duplicate returns `409`. **Next steps:** * [List Monitors](/api-reference/monitors/list) to view account monitors. * [Get Monitor](/api-reference/monitors/twitter-account-monitor-status) to fetch one monitor. * [Update Monitor](/api-reference/monitors/update) to pause or resume checks. * [Create Webhook](/api-reference/webhooks/create) to receive events. * [List Webhooks](/api-reference/webhooks/list) to check subscriptions. * [List Events](/api-reference/events/list) to audit stored events. * [Get Event](/api-reference/events/get) to inspect one event. * [List Deliveries](/api-reference/webhooks/deliveries) to inspect webhook attempts. # Twitter Keyword Monitor API & Real-time Tweet Alerts Source: https://docs.xquik.com/api-reference/monitors/create-keyword POST /monitors/keywords Create a 1-second keyword monitor for an X search query. Store matching tweet events and deliver selected events to signed webhooks. See event fields. ```json theme={null} { "id": "21", "query": "xquik OR \"x api\"", "eventTypes": [ "tweet.new" ], "isActive": true, "createdAt": "2025-01-15T12:00:00Z", "nextBillingAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "monitor_already_exists", "message": "Monitor already exists." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
Create a Twitter keyword monitor when one search query needs continuous tweet checks. Track brand mentions, handles, hashtags, products, campaigns, replies, reposts, links, or media. Store its ID, query, event types, billing time, and webhook destinations. **Requires 22 available credits** - active keyword monitors bill 21 credits per hour Keyword monitors are unlimited. Active monitors check every 1 second. Webhook and event deliveries are included in active monitor billing. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/monitors/keywords \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "query": "xquik api", "eventTypes": ["tweet.new"] }' | jq -c '{ keyword_monitor_id: .id, query: .query, event_types: .eventTypes, is_active: .isActive, created_at: .createdAt, next_billing_at: .nextBillingAt, verify_endpoint: "/api/v1/monitors/keywords/\(.id)", update_endpoint: "/api/v1/monitors/keywords/\(.id)", delete_endpoint: "/api/v1/monitors/keywords/\(.id)", events_endpoint: "/api/v1/events?keywordMonitorId=\(.id)", event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/monitors/keywords", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ query: "xquik api", eventTypes: ["tweet.new"], }), }); const monitor = await response.json(); const monitorState = { keyword_monitor_id: monitor.id, query: monitor.query, event_types: monitor.eventTypes, is_active: monitor.isActive, created_at: monitor.createdAt, next_billing_at: monitor.nextBillingAt, verify_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, update_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, delete_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, events_endpoint: `/api/v1/events?keywordMonitorId=${monitor.id}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(monitorState)}\n`); ``` ```python Python theme={null} import json import requests response = requests.post( "https://xquik.com/api/v1/monitors/keywords", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"query": "xquik api", "eventTypes": ["tweet.new"]}, ) monitor = response.json() monitor_state = { "keyword_monitor_id": monitor["id"], "query": monitor["query"], "event_types": monitor["eventTypes"], "is_active": monitor["isActive"], "created_at": monitor["createdAt"], "next_billing_at": monitor["nextBillingAt"], "verify_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "update_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "delete_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "events_endpoint": f"/api/v1/events?keywordMonitorId={monitor['id']}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(monitor_state)) ``` The cURL, Node.js, and Python examples convert the created or reactivated keyword monitor into one state row. Store `keyword_monitor_id`, `query`, `event_types`, `is_active`, `next_billing_at`, `verify_endpoint`, `update_endpoint`, `delete_endpoint`, `events_endpoint`, `event_detail_endpoint_pattern`, `webhooks_endpoint`, and `deliveries_endpoint_pattern` before routing alerts. ## Keyword Monitor Handoff Use `POST /monitors/keywords` when a queue, CRM, warehouse, Slack alert, or agent needs 1-second checks for one X search query. Create the monitor first. Then create a signed webhook with [`POST /webhooks`](/api-reference/webhooks/create). Test it with [`POST /webhooks/{id}/test`](/api-reference/webhooks/test). Store `id` as `keyword_monitor_id`. Use [Get Keyword Monitor](/api-reference/monitors/get-keyword) to verify state, [Update Keyword Monitor](/api-reference/monitors/update-keyword) to pause or resume, and [Delete Keyword Monitor](/api-reference/monitors/delete-keyword) only when the query should stop permanently. Store `query`; Xquik includes it on keyword monitor events and signed webhook payloads. Store `eventTypes`; keep [List Webhooks](/api-reference/webhooks/list) subscriptions aligned so expected tweets deliver. Read `isActive` and `nextBillingAt` before enabling alerts or estimating hourly monitor burn. Use `monitorType: "keyword"`, `keywordMonitorId`, and `query` from [List Events](/api-reference/events/list) to join stored events back to the monitor. Use [Get Event](/api-reference/events/get) for one event's full payload. Use `deliveryId` for receiver idempotency and [List Deliveries](/api-reference/webhooks/deliveries) for delivery audit rows. Join delivery `streamEventId` to event IDs. Do not use `x_event_id` as the delivery join key. Active keyword monitors check every 1 second and cost 21 credits per active monitor-hour. Creation or reactivation requires 22 available credits. Pause with [Update Keyword Monitor](/api-reference/monitors/update-keyword) and `{ "isActive": false }` when the alert should stop. ## How Do I Monitor Twitter Keywords With an API? Use one focused query for each alert purpose. First, write the exact words, phrases, handles, or hashtags requiring alerts. Then select the tweet event types that should reach downstream workers. Create the monitor and store its returned ID immediately. Next, connect a signed webhook for the selected event types. Send a signed test before accepting production alerts. Store each event before returning a successful receiver response. Use the event ID and delivery ID for separate idempotency checks. This workflow helps applications monitor Twitter keywords without maintaining a stream connection. The Twitter keyword monitor checks its stored query every second. Matching tweets become stored events and signed webhook deliveries. Use [List Events](/api-reference/events/list) when a webhook consumer needs recovery. ## How Do I Choose Twitter Keywords to Monitor? Start with terms that represent one operational decision. Support teams can watch product names, error phrases, or direct handles. Campaign teams can watch campaign hashtags and reply language. Developers can watch API names, integration phrases, or release mentions. Prefer an exact phrase when word order changes meaning. Use `OR` when any listed term should match. Use a space when every listed term must appear. Use a leading minus sign to exclude known noise. Group mixed `OR` conditions with parentheses. For example, monitor direct mentions with `@xquik`. Monitor a phrase with `"x api"`. Combine related terms with `(@xquik OR "xquik api")`. Exclude reposts with `-is:retweet` when repost alerts add no value. Test the proposed query with [Search Tweets](/api-reference/x/search-tweets) first. Review returned tweets before enabling continuous keyword monitoring. Narrow irrelevant matches before paying for an active monitor. Never add undocumented operators merely to increase apparent coverage. ## How Do I Track Twitter Mentions and Brand Keywords? Include the exact handle when direct mentions require action. Add the brand name when plain-text mentions also matter. Add specific product names only when workers own those alerts. Keep unrelated brands in separate monitors and queues. Tracking mentions requires stable evidence from every matched tweet. Store the tweet ID, author ID, event type, query, and timestamp. Keep `keywordMonitorId` beside each stored event. This join shows which query matched the tweet. The selected query monitors conversations across public matching tweets. It does not calculate sentiment or brand reputation. It also cannot reveal private tweets or direct messages. Treat mentions of your brand as matched tweets, not inferred opinions. ## When Should I Use Keyword Alerts, Tweet Search, or Account Monitoring? Choose the surface that matches the required time range and scope. | Requirement | Use | Result | | --------------------------------------- | -------------------------------------------------------- | -------------------------------------------------- | | Continuous matches across many accounts | Keyword monitor | Stored matching tweet events and real time alerts. | | Historical or on-demand query results | [Search Tweets](/api-reference/x/search-tweets) | One cursor-paginated tweet result set. | | Selected changes from one known account | [Create Account Monitor](/api-reference/monitors/create) | Tweet and profile events from that username. | A focused Twitter monitoring tool should not mix these three scopes. Use Twitter search for backfill and investigation. Use an account monitor for one known profile. Use keyword alerts for continuous matches across public tweets. Brand monitoring often needs both keyword and account monitors. Keep their event IDs, monitor IDs, and queue routes separate. That separation prevents one alert source from impersonating another. ## How Do Real-Time Tweet Alerts Reach My Application? Create the keyword monitor before registering its webhook destination. Subscribe the webhook to the same selected event types. Then send a signed test to validate the receiver. Verify every signature before parsing the request body. Store `deliveryId` before queuing downstream work. Store `streamEventId` before processing the matching tweet. Return success only after the durable queue write completes. The Events API provides recovery when live delivery fails. The Deliveries API shows webhook attempts for one registered endpoint. Use [Webhook Verification](/webhooks/verification) for receiver validation. Use [List Deliveries](/api-reference/webhooks/deliveries) for delivery audits. API access errors require different recovery actions. Replace invalid credentials after a `401` response. Add credits before retrying a `402` response. Respect `Retry-After` before repeating a `429` request. ## How Do I Backfill Tweets Before Starting Keyword Monitoring? Run the same query through Search Tweets before creating the monitor. Page until the response reports no next page. Store every tweet ID and its source query. Record the newest collected tweet timestamp as the handoff boundary. Then create the keyword monitor with the reviewed query. Deduplicate backfill and monitor events by stable tweet ID. Keep the stored monitor ID beside every continuous event. Do not reuse a search cursor after changing the query. A Twitter tracker must distinguish historical results from live alerts. Search results answer an on-demand time range. Monitor events represent continuous checks after activation. Combining both sources without a boundary creates duplicate tweets. ## How Do I Monitor Multiple Keyword Groups? Use one monitor when all terms share one alert action. Combine those terms with explicit `OR` grouping. Use separate monitors when terms require different owners or queues. Separate monitors also preserve distinct query evidence. Store each monitor ID, normalized query, event types, and billing time. Pause obsolete monitors instead of creating uncertain duplicates. List keyword monitors before repeating a timed-out create request. A paused duplicate can reactivate with the submitted event types. Review active monitors before each billing checkpoint. Every active keyword monitor adds its hourly charge. Pause queries that no longer trigger a real downstream action. Do not broaden queries merely to generate more alerts. ## How Do I Keep Twitter Keyword Alerts Actionable? Assign one owner and queue to each monitor ID. Route alerts by query, event type, and matched tweet ID. Keep support alerts separate from campaign or competitor alerts. Define the required action before activating the monitor. Review false matches from stored events at a regular interval. Add exact phrases or exclusions when irrelevant tweets repeat. Remove a term when it no longer supports an operational decision. Keep the previous query beside every configuration change. Do not delete evidence when a query changes. Stored events explain why earlier alerts reached their original queue. Record the change time and the responsible operator. Use a new monitor when ownership or alert purpose changes completely. Measure matched tweets, accepted alerts, rejected alerts, and processing failures. Those counts reveal query precision without inventing sentiment scores. Pause noisy monitors while operators correct their stored queries. Resume only after a reviewed search returns useful tweets. ## What Does a Twitter Keyword Monitor Not Provide? A keyword monitor does not return a complete historical archive. It does not provide follower, following, like, or bookmark changes. It does not verify giveaway follows, replies, or reposts by itself. It does not calculate sentiment, reach, or engagement quality. Use [Followers](/api-reference/x/followers) for follower snapshots. Use [Following](/api-reference/x/following) for following snapshots. Use focused tweet endpoints for replies, quotes, and retweeters. Use Search Tweets when an operator needs historical matching tweets. This endpoint remains a query-specific Twitter tracker with webhook delivery. It stores concrete tweet matches instead of inferred marketing conclusions. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body X search query to monitor. Whitespace is normalized. Maximum length is 512 characters. Array of event types to subscribe to. At least 1 required. See [Valid Event Types](#valid-event-types) below. ## Valid Event Types Valid keyword monitor types: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, `tweet.longform`. Matching tweet returned by the query. Used when no reply, quote, or retweet signal is present. Matching quote tweet returned by the query. Include this when quote activity should create keyword monitor events and webhook deliveries. Matching reply returned by the query. Include this when support routing, conversation tracking, or alerting needs replies. Matching retweet returned by the query. Include this when repost activity should create keyword monitor events and webhook deliveries. ## Response ### 201 Created Unique keyword monitor ID. Normalized query being monitored. Event types this monitor is subscribed to. Whether the monitor is currently active. ISO 8601 creation timestamp. Next hourly credit charge time. New active monitors are due immediately. ```json theme={null} { "id": "21", "query": "xquik api", "eventTypes": ["tweet.new"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T10:30:00.000Z" } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid query or event types" } ``` Missing query, query longer than 512 characters, or invalid `eventTypes`. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 402 Payment Required ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits" } ``` At least 22 available credits are required before creating or reactivating an active keyword monitor. Possible errors include `no_credits` and `insufficient_credits`. ### 409 Duplicate ```json theme={null} { "error": "monitor_already_exists", "message": "Monitor already exists." } ``` An active keyword monitor already exists for this normalized query. Use [Update Keyword Monitor](/api-reference/monitors/update-keyword) to change event types or pause it. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. If a keyword monitor for the same query exists but is paused, creating it again reactivates that monitor with the new event types. **Next steps:** [List Keyword Monitors](/api-reference/monitors/list-keywords), [Get Keyword Monitor](/api-reference/monitors/get-keyword), [Update Keyword Monitor](/api-reference/monitors/update-keyword), [Create Webhook](/api-reference/webhooks/create), [List Webhooks](/api-reference/webhooks/list), [List Events](/api-reference/events/list), [Get Event](/api-reference/events/get), or [List Deliveries](/api-reference/webhooks/deliveries). # Delete Twitter Keyword Monitor & Stop Webhooks Source: https://docs.xquik.com/api-reference/monitors/delete-keyword DELETE /monitors/keywords/{id} Delete a keyword monitor, stop polling its X search query, and prevent new matching tweet events and webhook deliveries. Includes signature and retry examples. ```json theme={null} { "success": true } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
Delete a keyword monitor only when its tweet query should stop permanently. Pause it instead when future monitoring might resume. **Free** - does not consume credits ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/monitors/keywords/21 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const monitorId = "21"; const response = await fetch( `https://xquik.com/api/v1/monitors/keywords/${monitorId}`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }, ); const result = await response.json(); const deletionReceipt = { keyword_monitor_id: monitorId, success: result.success === true, verify_endpoint: `/api/v1/monitors/keywords/${monitorId}`, list_endpoint: "/api/v1/monitors/keywords", }; process.stdout.write(`${JSON.stringify(deletionReceipt)}\n`); ``` ```python Python theme={null} import json import requests monitor_id = "21" response = requests.delete( f"https://xquik.com/api/v1/monitors/keywords/{monitor_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) result = response.json() deletion_receipt = { "keyword_monitor_id": monitor_id, "success": result["success"] is True, "verify_endpoint": f"/api/v1/monitors/keywords/{monitor_id}", "list_endpoint": "/api/v1/monitors/keywords", } print(json.dumps(deletion_receipt)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type DeleteResult struct { Success bool `json:"success"` } type KeywordMonitorDeletion struct { KeywordMonitorID string `json:"keyword_monitor_id"` Success bool `json:"success"` VerifyEndpoint string `json:"verify_endpoint"` ListEndpoint string `json:"list_endpoint"` } func main() { monitorID := "21" req, err := http.NewRequest( "DELETE", "https://xquik.com/api/v1/monitors/keywords/"+monitorID, nil, ) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result DeleteResult if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { log.Fatal(err) } receipt := KeywordMonitorDeletion{ KeywordMonitorID: monitorID, Success: result.Success, VerifyEndpoint: "/api/v1/monitors/keywords/" + monitorID, ListEndpoint: "/api/v1/monitors/keywords", } if err := json.NewEncoder(os.Stdout).Encode(receipt); err != nil { log.Fatal(err) } } ``` The Node.js, Python, and Go examples convert the delete response into one receipt row. Store `keyword_monitor_id`, `success`, `verify_endpoint`, and `list_endpoint`, then verify that the deleted ID no longer appears in the list and that the verify endpoint returns `404`. ## Remove one keyword without deleting the monitor Use this route when the monitor should continue with fewer search terms. Confirm both the monitor ID and keyword ID before deletion. Save the keyword text in your approval record. The response identifies the removed keyword. Other keywords and monitor delivery settings remain a separate concern. Use a full monitor deletion only when the entire monitor should stop. Do not delete the monitor to remove one unwanted keyword. ## Path parameters The unique keyword monitor ID. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Always `true` on successful deletion. ```json theme={null} { "success": true } ``` ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided monitor ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Monitor not found" } ``` No keyword monitor exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Deletion handoff Use this endpoint when a keyword query should stop permanently. Use [Update Keyword Monitor](/api-reference/monitors/update-keyword) with `isActive: false` when you only need to pause alerts and keep the monitor available. Delete removes the keyword monitor. Store returned `success` before treating the deleted ID as permanently removed. The deleted ID cannot be fetched, updated, resumed, or billed again. Stored events and webhook delivery records tied to this keyword monitor are removed with it. Export or reconcile records before deletion. Use `PATCH /monitors/keywords/{id}` with `isActive: false` to stop future polling, alerts, and hourly billing while preserving the monitor record. Call [List Keyword Monitors](/api-reference/monitors/list-keywords) after deletion. [Get Keyword Monitor](/api-reference/monitors/get-keyword) should return `404` for the deleted ID. Create a new keyword monitor when the X search query changes. Store the new `id`, `query`, `eventTypes`, `isActive`, and `nextBillingAt`. Existing webhook endpoints remain configured. Keep their `eventTypes` aligned, then run [Test Webhook](/api-reference/webhooks/test) before relying on new keyword monitor alerts. The keyword monitor is deleted and its stored events are removed with it. Pause with `isActive: false` if you want to stop new checks without deleting the monitor. **Related:** [List Keyword Monitors](/api-reference/monitors/list-keywords) to verify removal, or [Create Keyword Monitor](/api-reference/monitors/create-keyword) to track a new query. # Delete Twitter Account Monitor & Stop Monitoring Source: https://docs.xquik.com/api-reference/monitors/delete-twitter-account-monitor DELETE /monitors/{id} Delete one Xquik Twitter account monitor, stop future tweet and profile checks, and safely remove its stored events without deleting the tracked X account. ```json theme={null} { "success": true } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
This endpoint deletes one saved Xquik account monitor. It stops future tweet and profile checks. It removes stored events and linked webhook delivery records. The tracked X account and its profile remain unchanged. Its tweets, followers, and following list also remain unchanged. This operation is permanent. Pause the monitor when its alerts may resume. **Free** - does not consume credits ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/monitors/7 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq -c '{ monitor_id: "7", success: .success == true, verify_endpoint: "/api/v1/monitors/7", list_endpoint: "/api/v1/monitors", events_endpoint: "/api/v1/events?monitorId=7", event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const monitorId = "7"; const response = await fetch(`https://xquik.com/api/v1/monitors/${monitorId}`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const result = await response.json(); const deletionReceipt = { monitor_id: monitorId, success: result.success === true, verify_endpoint: `/api/v1/monitors/${monitorId}`, list_endpoint: "/api/v1/monitors", events_endpoint: `/api/v1/events?monitorId=${monitorId}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(deletionReceipt)}\n`); ``` ```python Python theme={null} import json import requests monitor_id = "7" response = requests.delete( f"https://xquik.com/api/v1/monitors/{monitor_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) result = response.json() deletion_receipt = { "monitor_id": monitor_id, "success": result["success"] is True, "verify_endpoint": f"/api/v1/monitors/{monitor_id}", "list_endpoint": "/api/v1/monitors", "events_endpoint": f"/api/v1/events?monitorId={monitor_id}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(deletion_receipt)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type DeleteResult struct { Success bool `json:"success"` } type AccountMonitorDeletion struct { MonitorID string `json:"monitor_id"` Success bool `json:"success"` VerifyEndpoint string `json:"verify_endpoint"` ListEndpoint string `json:"list_endpoint"` EventsEndpoint string `json:"events_endpoint"` EventDetailEndpointPattern string `json:"event_detail_endpoint_pattern"` WebhooksEndpoint string `json:"webhooks_endpoint"` DeliveriesEndpointPattern string `json:"deliveries_endpoint_pattern"` } func main() { monitorID := "7" req, err := http.NewRequest( "DELETE", "https://xquik.com/api/v1/monitors/"+monitorID, nil, ) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result DeleteResult if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { log.Fatal(err) } receipt := AccountMonitorDeletion{ MonitorID: monitorID, Success: result.Success, VerifyEndpoint: "/api/v1/monitors/" + monitorID, ListEndpoint: "/api/v1/monitors", EventsEndpoint: "/api/v1/events?monitorId=" + monitorID, EventDetailEndpointPattern: "/api/v1/events/{event_id}", WebhooksEndpoint: "/api/v1/webhooks", DeliveriesEndpointPattern: "/api/v1/webhooks/{webhook_id}/deliveries", } if err := json.NewEncoder(os.Stdout).Encode(receipt); err != nil { log.Fatal(err) } } ``` The cURL, Node.js, Python, and Go examples convert the delete response into one receipt row. Store `monitor_id`, `success`, `verify_endpoint`, `list_endpoint`, `events_endpoint`, `event_detail_endpoint_pattern`, `webhooks_endpoint`, and `deliveries_endpoint_pattern`, then verify that the deleted ID no longer appears in the list and that the verify endpoint returns `404`. ## Delete a Twitter Account Monitor Safely Delete only when the account monitor should never run again. Confirm the monitor ID, X username, X user ID, and event types first. Save required tweet and profile events before deletion. Record the approver and final monitor identity in your own system. Use keyword monitor deletion only for a keyword monitor. Keep other account and keyword monitors when their checks must continue. ## Path parameters The unique monitor ID. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Always `true` on successful deletion. ```json theme={null} { "success": true } ``` ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided monitor ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Monitor not found" } ``` No monitor exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Follow the `Retry-After` interval before another request. ## Does This Delete the X Account or Its Tweets? No. This route deletes an Xquik account monitor only. This route cannot deactivate X accounts. It does not remove tweets, followers, following, replies, or likes. It also keeps reposts, media, lists, communities, and profile details. The tracked X account remains unchanged. Its public activity remains available through the applicable X API endpoints. Only Xquik's saved monitor and related monitor records are removed. ## Should I Pause or Delete Account Monitoring? Pause when alerts may resume. Send `isActive: false` through [Update Monitor](/api-reference/monitors/update). Pausing preserves the monitor ID, profile, event filters, and stored history. It stops future checks, deliveries, and hourly monitor billing. Delete when monitoring is permanently retired. Deletion removes the saved monitor and its stored history. A replacement monitor receives another ID. | Decision | Use it when | Result | | -------------- | --------------------------------------------------- | ------------------------------ | | Pause | Account monitoring may resume. | Keeps the monitor and history. | | Update filters | Only future tweet or profile coverage changes. | Replaces `eventTypes`. | | Delete | The monitor and its history are no longer required. | Permanently removes both. | ## What Does Monitor Deletion Remove? Deleting the monitor removes its settings and stored tweet events. It also removes stored profile events, search records, and webhook delivery attempts. These records depend on the deleted monitor. Webhook endpoints remain configured. Existing endpoints can serve another monitor with matching event subscriptions. Other account monitors and keyword monitors remain active. ## How Do I Verify Monitor Deletion? 1. Save the monitor ID before sending `DELETE`. 2. Require `200` with `{ "success": true }`. 3. Read the same monitor and expect `404 not_found`. 4. List monitors and confirm the ID is absent. 5. Treat another `DELETE` response of `404` as already removed. Do not treat a network timeout as deletion proof. Confirm the monitor state before another deletion request. Wait for `Retry-After` after a `429` response. ## Deletion handoff Use this endpoint when a tracked account should stop permanently. Use [Update Monitor](/api-reference/monitors/update) with `isActive: false` when you only need to pause alerts and keep the monitor available. | Account monitor deletion check | Source | Completion rule | | ------------------------------ | ------------------------------- | --------------------------------------------- | | Delete receipt | `success` | Continue only when the response is `true`. | | Monitor inventory | `GET /monitors` | Confirm the deleted monitor ID is absent. | | Detail lookup | `GET /monitors/{id}` | Expect `404 not_found` after deletion. | | Stored events | `GET /events?monitorId={id}` | Save required tweet and profile events first. | | Delivery history | `GET /webhooks/{id}/deliveries` | Preserve required delivery evidence first. | | Temporary stop | `PATCH /monitors/{id}` | Use `isActive: false` instead of deletion. | ## Plan Retention Before Monitor Deletion Deletion cascades through stored events and linked delivery records. Decide which records your team must keep before sending `DELETE`. Export required tweet events, profile events, and relationship events first. Keep each event's `id`, `type`, `monitorId`, `username`, and `occurredAt`. Preserve the complete `data` object for downstream tweet or profile processing. Support teams may also need webhook delivery evidence. Save delivery status, attempt counts, receiver responses, errors, and timestamps before deletion. Store these records in your approved system. Never place API keys in exports. Deletion does not create an archive. Xquik cannot return deleted monitor events through the events endpoints later. Finish retention checks before approval. ## Select the Exact Twitter Account Monitor Never select a monitor from a display label alone. Read the account monitor and compare its `id`, `username`, `xUserId`, `eventTypes`, and `isActive` values. Use [List Monitors](/api-reference/monitors/list) when the monitor ID is unknown. Then use [Get Monitor](/api-reference/monitors/twitter-account-monitor-status) to confirm the selected record. Record the final identity beside the approval. Delete account monitors and keyword monitors through their matching routes. Use [Delete Keyword Monitor](/api-reference/monitors/delete-keyword) for a keyword query. Never send a keyword monitor ID to this account monitor endpoint. The route deletes by Xquik monitor ID. It does not delete by X username. This boundary prevents an account name from selecting several monitor records. ## Preserve Tweet and Profile Events Call [List Events](/api-reference/events/list) with the selected `monitorId`. Follow every cursor until `hasMore` becomes `false`. Complete every cursor page to include older tweet and profile events. Use [Get Event](/api-reference/events/get) for every event needing full detail. Keep `event.id` as the stable Xquik event identifier. Keep `event.type` to separate tweets, followers, profile changes, and relationship changes. Store `occurredAt` with each event payload. It records when Xquik observed the event. Keep the monitor ID with every exported row. This association supports later reconciliation after the monitor record disappears. Validate the export before deletion. Compare exported IDs with the final list response. Retry incomplete pages before approving monitor removal. ## Preserve Webhook Delivery Evidence List configured endpoints through [List Webhooks](/api-reference/webhooks/list). Then call [List Deliveries](/api-reference/webhooks/deliveries) for each relevant endpoint. Join each delivery's `streamEventId` to the exported event `id`. Do not use `x_event_id` as the delivery join key. That value identifies an external event, not the stored Xquik event row. Keep `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`. These fields explain successful deliveries, receiver failures, and exhausted attempts. Deleting the monitor removes linked delivery attempts. It does not remove the webhook endpoint. Reuse that endpoint when another monitor emits matching event types. Test the endpoint before depending on new alerts. ## Handle Every Delete Response A `200` response confirms the deletion. Require `success: true` before closing the workflow. Store the monitor ID beside that receipt. A `400` response means the ID format is invalid. Correct the path value before another request. Do not substitute an X username for the monitor ID. A `401` response means authentication failed. Replace the invalid API key or session. Never expose that credential in logs or support tickets. Treat a `404` response as an unavailable monitor. It may already be deleted. It may also belong to another account. Verify the account context before proceeding. A `429` response means the rate limit blocked the request. Read `Retry-After`, wait for that interval, and check the monitor again. ## Resolve an Unknown Delete Result A client timeout does not prove failure. The server may have completed the deletion before the connection ended. Read the same monitor ID after the timeout. A `404` response confirms that the record is unavailable. A successful read shows that the monitor still exists. List account monitors as a second check. Confirm that the deleted ID is absent. Do not rely on an empty event search alone. An event filter can be wrong while the monitor still exists. Repeated deletion leaves the final state unchanged. However, the response changes. The first completed request returns `200`; later requests return `404` because the monitor no longer exists. ## Recreate Monitoring After Deletion A deleted account monitor cannot resume. Create another monitor when checks must restart. The replacement receives a different monitor ID. Use the saved X username, X user ID, and event types for reconstruction. Review those values before creating the replacement. Do not assume the old filters still match the current workflow. Existing webhook endpoints remain available. Confirm their event subscriptions before connecting the replacement monitor. Run [Test Webhook](/api-reference/webhooks/test) to validate receiver access and response handling. Update downstream jobs with the new monitor ID. Replace stored event filters, verification links, and support references. The deleted ID will continue returning `404`. ## Account Monitor Deletion Checklist 1. Confirm the monitor ID, X username, and X user ID. 2. Review `eventTypes`, `isActive`, and the deletion reason. 3. Export every required tweet and profile event. 4. Preserve required webhook delivery attempts and receiver results. 5. Obtain approval for permanent removal. 6. Send `DELETE` and require `200` with `success: true`. 7. Read the deleted ID and require `404 not_found`. 8. List monitors and confirm that the ID is absent. 9. Update downstream references to the removed monitor. Pause with `isActive: false` when monitoring may resume. Delete only after the retention, approval, and verification steps finish. ## Related Account Monitor Operations Use [List Monitors](/api-reference/monitors/list) to find and verify account monitors. Use [Get Monitor](/api-reference/monitors/twitter-account-monitor-status) to confirm the selected ID and its final `404` response. Use [List Events](/api-reference/events/list) and [Get Event](/api-reference/events/get) to retain tweet and profile evidence. Use [List Webhooks](/api-reference/webhooks/list) and [List Deliveries](/api-reference/webhooks/deliveries) for delivery evidence. Use [Create Monitor](/api-reference/monitors/create) when a replacement account monitor is required. Store the new ID before restoring dependent workflows. # Twitter Keyword Monitor Status & Query Checks Source: https://docs.xquik.com/api-reference/monitors/get-keyword GET /monitors/keywords/{id} Get one keyword monitor's X query, tracked event types, active state, polling interval, creation time, and latest event checkpoint. See request fields. ```json theme={null} { "id": "21", "query": "xquik OR \"x api\"", "eventTypes": [ "tweet.new" ], "isActive": true, "createdAt": "2025-01-15T12:00:00Z", "nextBillingAt": "2025-01-15T13:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Inspect One Twitter Keyword Monitor Use this route when a workflow already stores one keyword monitor ID. It returns the exact X search query, tracked tweet events, active state, creation time, and next billing checkpoint. Use the list route when the ID is unknown. Check `query` before routing new matches. It preserves the Twitter keyword search that created the monitor. Check `eventTypes` before assuming replies, quotes, reposts, or other tweet events are enabled. | Single-monitor question | Response field | Incident decision | | ---------------------------- | ----------------------------------- | ---------------------------------------------------- | | Is this the intended search? | `id` and `query` | Stop if either value differs from the stored alert. | | Can it capture new tweets? | `isActive` | Resume only when future polling is required. | | Which tweet events qualify? | `eventTypes` | Compare the exact types with webhook subscriptions. | | When is the next charge? | `nextBillingAt` | Confirm credits before the billing checkpoint. | | Where are matching tweets? | `GET /events?keywordMonitorId={id}` | Inspect stored events separately from configuration. | Store `id`, `query`, `eventTypes`, `isActive`, and `nextBillingAt` together. Pass the same monitor ID to event filters, updates, deletion, and webhook workflows. This keeps every Twitter monitor handoff tied to one tracked search. Use this status check before pausing or editing a monitor. A successful read does not prove a matching tweet exists. Read the events endpoint for captured tweets, then open one event for its complete payload. **Free** - does not consume credits ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/monitors/keywords/21 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq -c '{ keyword_monitor_id: .id, query: .query, event_types: .eventTypes, is_active: .isActive, next_billing_at: .nextBillingAt, update_endpoint: "/api/v1/monitors/keywords/\(.id)", delete_endpoint: "/api/v1/monitors/keywords/\(.id)", events_endpoint: "/api/v1/events?keywordMonitorId=\(.id)", event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const monitorId = "21"; const response = await fetch( `https://xquik.com/api/v1/monitors/keywords/${monitorId}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }, ); const monitor = await response.json(); const monitorState = { keyword_monitor_id: monitor.id, query: monitor.query, event_types: monitor.eventTypes, is_active: monitor.isActive, created_at: monitor.createdAt, next_billing_at: monitor.nextBillingAt, update_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, delete_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, events_endpoint: `/api/v1/events?keywordMonitorId=${monitor.id}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(monitorState)}\n`); ``` ```python Python theme={null} import json import requests monitor_id = "21" response = requests.get( f"https://xquik.com/api/v1/monitors/keywords/{monitor_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) monitor = response.json() monitor_state = { "keyword_monitor_id": monitor["id"], "query": monitor["query"], "event_types": monitor["eventTypes"], "is_active": monitor["isActive"], "created_at": monitor["createdAt"], "next_billing_at": monitor["nextBillingAt"], "update_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "delete_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "events_endpoint": f"/api/v1/events?keywordMonitorId={monitor['id']}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(monitor_state)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type KeywordMonitor struct { ID string `json:"id"` Query string `json:"query"` EventTypes []string `json:"eventTypes"` IsActive bool `json:"isActive"` CreatedAt string `json:"createdAt"` NextBillingAt string `json:"nextBillingAt"` } type KeywordMonitorState struct { KeywordMonitorID string `json:"keyword_monitor_id"` Query string `json:"query"` EventTypes []string `json:"event_types"` IsActive bool `json:"is_active"` CreatedAt string `json:"created_at"` NextBillingAt string `json:"next_billing_at"` UpdateEndpoint string `json:"update_endpoint"` DeleteEndpoint string `json:"delete_endpoint"` EventsEndpoint string `json:"events_endpoint"` EventDetailEndpointPattern string `json:"event_detail_endpoint_pattern"` WebhooksEndpoint string `json:"webhooks_endpoint"` DeliveriesEndpointPattern string `json:"deliveries_endpoint_pattern"` } func main() { monitorID := "21" req, err := http.NewRequest("GET", "https://xquik.com/api/v1/monitors/keywords/"+monitorID, nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var monitor KeywordMonitor if err := json.NewDecoder(resp.Body).Decode(&monitor); err != nil { log.Fatal(err) } state := KeywordMonitorState{ KeywordMonitorID: monitor.ID, Query: monitor.Query, EventTypes: monitor.EventTypes, IsActive: monitor.IsActive, CreatedAt: monitor.CreatedAt, NextBillingAt: monitor.NextBillingAt, UpdateEndpoint: "/api/v1/monitors/keywords/" + monitor.ID, DeleteEndpoint: "/api/v1/monitors/keywords/" + monitor.ID, EventsEndpoint: "/api/v1/events?keywordMonitorId=" + monitor.ID, EventDetailEndpointPattern: "/api/v1/events/{event_id}", WebhooksEndpoint: "/api/v1/webhooks", DeliveriesEndpointPattern: "/api/v1/webhooks/{webhook_id}/deliveries", } if err := json.NewEncoder(os.Stdout).Encode(state); err != nil { log.Fatal(err) } } ``` The cURL, Node.js, Python, and Go examples convert the fetched keyword monitor into one state snapshot row. Store `keyword_monitor_id`, `query`, `event_types`, `is_active`, `next_billing_at`, `update_endpoint`, `delete_endpoint`, `events_endpoint`, `event_detail_endpoint_pattern`, `webhooks_endpoint`, and `deliveries_endpoint_pattern` before changing filters, pausing alerts, or reconciling webhooks. ## State handoff Use `GET /monitors/keywords/{id}` before changing routing, billing checks, or alert state for one keyword monitor. The endpoint returns the current stored monitor for your account only; deleted or cross-account IDs return `404`. | Keyword monitor column | Response source | Decision rule | | ---------------------- | --------------- | --------------------------------------------------- | | Monitor ID | `id` | Use this ID for updates, events, and deletion. | | X search query | `query` | Compare the stored query with the intended search. | | Tweet event filter | `eventTypes` | Align webhook subscriptions with these event types. | | Polling state | `isActive` | Resume only when future tweet checks are required. | | Creation time | `createdAt` | Preserve the monitor configuration timestamp. | | Billing checkpoint | `nextBillingAt` | Review credits before the next active charge. | | Missing monitor | `404 not_found` | Stop changes for deleted or cross-account IDs. | Treat `query` and `eventTypes` as the active matching contract. Mirror `eventTypes` into [List Webhooks](/api-reference/webhooks/list) before relying on signed alerts. Use `isActive` to decide whether the monitor should poll and bill. Use [Update Keyword Monitor](/api-reference/monitors/update-keyword) to pause or resume it. Read `nextBillingAt` before credit alerts, budget checks, or account handoffs. Paused monitors stay visible but do not add hourly monitor burn. Use `id` as `keywordMonitorId` with [List Events](/api-reference/events/list) to reconcile stored events and webhook deliveries for this query. Use [Get Event](/api-reference/events/get) for one event's full payload. Use [List Deliveries](/api-reference/webhooks/deliveries) when webhook delivery evidence must be retained. Join delivery `streamEventId` to event IDs. Do not use `x_event_id` as the delivery join key. Use [Delete Keyword Monitor](/api-reference/monitors/delete-keyword) only when the query should stop permanently. Export event and delivery evidence first when support or audit workflows need history. ## Verify One Alert Before Incident Review Inspect one stored monitor before investigating a missed or unexpected alert. Start with the monitor ID recorded by the alerting system. A list response can hide which row the workflow actually used. Compare the returned `query` with the intended Twitter search expression. Check capitalization, quoted phrases, exclusions, and operators. Store the returned value as evidence. Do not reconstruct it from a dashboard label. Next, compare `eventTypes` with the event under review. A monitor configured only for `tweet.new` cannot explain an expected profile-change alert. Update the filter only after recording its current value. Read `isActive` before examining an empty event window. A paused monitor stays available through this endpoint. It does not create future matching events. Check `nextBillingAt` when the monitor should be active. Use the returned ID to query stored events. Keep these outcomes separate: * The monitor exists, but its query does not match the expected tweet. * The query matches, but the required event type is absent. * The event type exists, but the monitor is paused. * The monitor is active, but no stored event matches the review window. * A stored event exists, but its webhook delivery needs inspection. Open the matching event before diagnosing webhook delivery. Then join its event ID with `streamEventId` from delivery records. This separates search matching from notification transport. Finish with a compact incident note. Record the monitor ID, exact query, active state, event types, event ID, and delivery result. That record lets another operator repeat the check without listing every monitor. ## Diagnose One Keyword Monitor Start with the returned monitor ID, query, event types, and active state. Check `nextBillingAt` and remaining credits before investigating a missing alert. Run the exact query through tweet search. Relevant results confirm query coverage, not webhook delivery. Inspect stored events by `keywordMonitorId`. Inspect delivery records by webhook ID. Keep `streamEventId` separate from each delivery ID. Do not edit the monitor during diagnosis. Capture its previous state before any approved change. ## Prove a Keyword Alert With Stored Evidence Build one evidence chain for the alert under review. Start with this endpoint's monitor response. Preserve its ID, exact query, active state, and event types. Run the exact query through [Tweet Search](/api-reference/x/search-tweets). Save matching Tweet IDs and creation times. A search match proves query coverage. It does not prove that a monitor stored an event. Next, list stored events for the same keyword monitor ID. Match the expected Tweet ID when the event payload exposes it. Preserve the event ID and event timestamp. An event proves monitor ingestion. It does not prove webhook delivery. Finally, inspect deliveries for the subscribed webhook. Join each delivery's `streamEventId` to the stored event ID. Record its status and attempt time. A delivery record proves notification handling. Keep three outcomes separate: * Search found the tweet, but no monitor event exists. * A monitor event exists, but no delivery references it. * A delivery exists, but the receiving application rejected it. Never report those outcomes as one generic alert failure. Each outcome needs a different correction. Query changes affect matching. Monitor state affects future event creation. Webhook work affects notification transport. Record the investigation window in UTC. Compare tweet, event, and delivery timestamps inside that window. Avoid using dashboard refresh time as evidence. Finish with one durable incident record. Include monitor ID, query, Tweet ID, event ID, webhook ID, and delivery result. Link every conclusion to a returned field. This makes another operator's review repeatable. ## Distinguish Configuration Drift From Missing Tweets Compare the returned query with the approved query character by character. Check quoted phrases, exclusions, hashtags, usernames, and search operators. One missing operator can change every matched tweet. Compare `eventTypes` with the approved event scope. Keep the full returned array. Do not summarize several values as a broad monitoring label. Check `isActive` and `nextBillingAt` together. A stored monitor can remain visible while paused. A future billing timestamp supports the next budget review. Neither field proves that a specific tweet created an event. Use [Update Account Monitor](/api-reference/monitors/update) only for account monitors. Keyword monitors use their dedicated update route. Keep monitor types separate when support tickets contain several IDs. ## Path parameters The unique keyword monitor ID. Returned when you [create a keyword monitor](/api-reference/monitors/create-keyword) or [list keyword monitors](/api-reference/monitors/list-keywords). ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Unique keyword monitor ID. Normalized X search query. Subscribed event types. Whether the monitor is currently active. ISO 8601 creation timestamp. Next hourly credit charge time for active monitor billing. ```json theme={null} { "id": "21", "query": "xquik OR \"x api\"", "eventTypes": ["tweet.new"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T11:30:00.000Z" } ``` ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided monitor ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Monitor not found" } ``` No keyword monitor exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [List Keyword Monitors](/api-reference/monitors/list-keywords), [Update Keyword Monitor](/api-reference/monitors/update-keyword), [Delete Keyword Monitor](/api-reference/monitors/delete-keyword), [List Events](/api-reference/events/list), [Get Event](/api-reference/events/get), [List Webhooks](/api-reference/webhooks/list), or [List Deliveries](/api-reference/webhooks/deliveries). # Twitter Account Monitoring List & Tracked Profiles Source: https://docs.xquik.com/api-reference/monitors/list GET /monitors List Twitter account monitors with tracked X profiles, tweet and profile event filters, active states, creation times, and billing checkpoints per monitor. ```json theme={null} { "monitors": [], "total": 0 } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Inventory Every Twitter Account Monitor Use `GET /monitors` to monitor multiple Twitter accounts. It returns one complete account-monitor inventory. The route lists every account monitor that belongs to the signed-in user. Each row identifies one tracked X profile. It also shows enabled tweet or profile events and the active state. The row includes its creation time. Check `nextBillingAt` for the next active-monitor charge. Use the single-monitor route for one known ID. Group rows by `username` or stable `xUserId`. Keep `eventTypes` with every row because two monitors can track different changes. Separate inactive monitors before calculating which profiles still produce events. Store `monitorId` before handing a row to event or webhook processing. Event queries use that ID to isolate tweets and profile changes. Webhook deliveries remain separate from monitor inventory. Review `nextBillingAt` before enabling more monitors. Listing is free, but active monitors consume credits each hour. Delete unused monitors. Do not call a monitor idle after one empty event window. **Free** - does not consume credits ## How Do You Monitor Multiple Twitter Accounts? Create one account monitor for each X username. Select only the required tweet and profile event types. Then call this route to rebuild the complete list. Store each returned `id` as the stable monitor key. Keep `xUserId` beside the username because an account can change its username. This inventory supports Twitter account monitoring across many tracked profiles. Use `isActive` to filter rows before estimating costs. Group active rows by owner, workflow, or webhook receiver. Keep paused rows in the same inventory because their configuration still exists. Use [List Events](/api-reference/events/list) to retrieve stored events for one monitor. Pass the returned monitor ID as `monitorId`. Use [List Webhooks](/api-reference/webhooks/list) to inspect receivers. Use [List Deliveries](/api-reference/webhooks/deliveries) to audit each delivery. This route lists monitor settings, not stored events. ## What Does the Monitor Inventory Include? Each row shows the monitor ID, username, and stable X user ID for account joins. The `eventTypes` array defines which tweet and profile changes matter. The `isActive` field separates running monitors from paused configurations. The timestamps show when Xquik created the monitor and its next billing point. Tweet filters cover new posts, replies, quotes, reposts, media, and links. They also cover polls, mentions, hashtags, and long-form posts. Profile filters include the avatar, banner, name, username, biography, and location. They also cover URL, verification, protection, pinned tweets, and account availability. Read `eventTypes` from every row. Do not assume every monitor uses identical filters. The response also includes `total`. Compare it with the emitted monitor count. A mismatch usually means the client dropped rows during local processing. The route returns up to 200 monitors and does not paginate. Treat one successful response as the complete server result for that request. ## Does This Route Return Tweets or Analytics? No. `GET /monitors` returns account-monitor configurations. It does not return tweet timelines, engagement totals, audience reports, or sentiment scores. Use [List Events](/api-reference/events/list) for stored tweet and profile events. Use [Get Event](/api-reference/events/get) to inspect one stored event. The route also does not publish or schedule posts. Use the [X Write API](/api-reference/x-write/create-tweet) for publishing workflows. Keep these responsibilities separate. Otherwise, a monitor inventory can become an event feed or publishing queue. ## How Do You Set Alerts for Several X Accounts? First, create one monitor per username. Give each monitor only the event types its receiver understands. Then create a webhook for the matching event types. Store the monitor ID with every workflow rule. This preserves the source when several profiles send similar events. Re-list monitors after every create, update, pause, or delete operation. Check that the intended row exists and has the expected `eventTypes`. Confirm `isActive` before waiting for a new alert. Paused monitors retain their settings. They stop polling until reactivated. Audit webhook deliveries separately. Join a delivery to its stored event with the documented event ID. Do not infer success from the monitor's active state. Polling state and webhook delivery status are separate. ## How Should Teams Manage Many Tracked Profiles? Assign one owner when managing multiple Twitter accounts. Store that owner outside the API response. Save the monitor ID, X user ID, selected event types, and active state. Add the owner and review date. Assign ownership by workflow. Support teams can process replies and mentions. Research teams can own new posts, quotes, or media events. Account teams can own profile-name, biography, verification, or availability changes. Match each assignment to the exact `eventTypes` returned for that monitor. Agencies should separate client profiles before reporting active-monitor costs. Review paused and active rows together, but count their costs separately. Record every approved state change. This history prevents one client cleanup from changing another client's monitor. ## Is Listing Twitter Account Monitors Free? Yes. This `GET` request consumes no credits. Active monitors cost 21 credits per monitor-hour. Paused monitors stay listed and stop active-monitor billing. Review `isActive` and `nextBillingAt` before enabling more profiles. Do not use a quiet event window as a cost signal. A valid monitor may receive no matching event during that period. Confirm its owner and selected filters before pausing it. Delete a monitor only when its configuration is no longer required. ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/monitors \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq -c '.monitors[] | { monitor_id: .id, username, x_user_id: .xUserId, event_types: .eventTypes, is_active: .isActive, created_at: .createdAt, next_billing_at: .nextBillingAt, monitor_detail_endpoint: ("/api/v1/monitors/" + .id), events_endpoint: ("/api/v1/events?monitorId=" + .id), event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/monitors", { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const payload = await response.json(); for (const monitor of payload.monitors) { const monitorRow = { monitor_id: monitor.id, username: monitor.username, x_user_id: monitor.xUserId, event_types: monitor.eventTypes, is_active: monitor.isActive, created_at: monitor.createdAt, next_billing_at: monitor.nextBillingAt, monitor_detail_endpoint: `/api/v1/monitors/${monitor.id}`, events_endpoint: `/api/v1/events?monitorId=${monitor.id}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(monitorRow)}\n`); } ``` ```python Python theme={null} import json import requests response = requests.get( "https://xquik.com/api/v1/monitors", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) payload = response.json() for monitor in payload["monitors"]: monitor_row = { "monitor_id": monitor["id"], "username": monitor["username"], "x_user_id": monitor["xUserId"], "event_types": monitor["eventTypes"], "is_active": monitor["isActive"], "created_at": monitor["createdAt"], "next_billing_at": monitor["nextBillingAt"], "monitor_detail_endpoint": f"/api/v1/monitors/{monitor['id']}", "events_endpoint": f"/api/v1/events?monitorId={monitor['id']}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(monitor_row)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type Monitor struct { ID string `json:"id"` Username string `json:"username"` XUserID string `json:"xUserId"` EventTypes []string `json:"eventTypes"` IsActive bool `json:"isActive"` CreatedAt string `json:"createdAt"` NextBillingAt string `json:"nextBillingAt"` } type MonitorListResponse struct { Monitors []Monitor `json:"monitors"` Total int `json:"total"` } type MonitorRow struct { DeliveriesEndpointPattern string `json:"deliveries_endpoint_pattern"` EventDetailEndpointPattern string `json:"event_detail_endpoint_pattern"` EventsEndpoint string `json:"events_endpoint"` EventTypes []string `json:"event_types"` CreatedAt string `json:"created_at"` IsActive bool `json:"is_active"` MonitorDetailEndpoint string `json:"monitor_detail_endpoint"` MonitorID string `json:"monitor_id"` NextBillingAt string `json:"next_billing_at"` Username string `json:"username"` WebhooksEndpoint string `json:"webhooks_endpoint"` XUserID string `json:"x_user_id"` } func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/monitors", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var payload MonitorListResponse if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { log.Fatal(err) } encoder := json.NewEncoder(os.Stdout) for _, monitor := range payload.Monitors { row := MonitorRow{ DeliveriesEndpointPattern: "/api/v1/webhooks/{webhook_id}/deliveries", EventDetailEndpointPattern: "/api/v1/events/{event_id}", EventsEndpoint: "/api/v1/events?monitorId=" + monitor.ID, EventTypes: monitor.EventTypes, CreatedAt: monitor.CreatedAt, IsActive: monitor.IsActive, MonitorDetailEndpoint: "/api/v1/monitors/" + monitor.ID, MonitorID: monitor.ID, NextBillingAt: monitor.NextBillingAt, Username: monitor.Username, WebhooksEndpoint: "/api/v1/webhooks", XUserID: monitor.XUserID, } if err := encoder.Encode(row); err != nil { log.Fatal(err) } } } ``` The Node.js, Python, and Go examples emit one structured monitor record. Save each record with its support, event, and webhook audit history. ## Inventory Handoff Use `GET /monitors` after create, update, pause, or delete operations to rebuild your account monitor inventory. The response returns up to 200 monitors ordered by creation time and a `total` count for the returned set. | Account monitor inventory column | Response source | Inventory check | | -------------------------------- | -------------------------- | ----------------------------------------------- | | Monitor ID | `monitors[].id` | Use this ID for status, updates, and events. | | X username | `monitors[].username` | Display the tracked account without `@`. | | X user ID | `monitors[].xUserId` | Use this stable ID for account joins. | | Event filter | `monitors[].eventTypes` | Compare these types with webhook subscriptions. | | Polling state | `monitors[].isActive` | Separate active and paused account monitors. | | Billing checkpoint | `monitors[].nextBillingAt` | Schedule the next active monitor credit check. | | Inventory count | `total` | Compare this count with emitted monitor rows. | Store each monitor's `id`, `username`, and `xUserId` with downstream CRM, warehouse, or queue records. Use [Get Monitor](/api-reference/monitors/twitter-account-monitor-status) with each `id` when a support workflow needs the latest event filter, active state, or billing checkpoint for one account monitor. Filter monitors where `isActive` is `true`. Each active account monitor bills 21 credits per active monitor-hour; use `nextBillingAt` to schedule credit checks or pause stale alerts. Compare each monitor's `eventTypes` with [List Webhooks](/api-reference/webhooks/list) before relying on signed alerts. Use `id` as `monitorId` with [List Events](/api-reference/events/list) to audit stored account monitor events. Open one returned ID with [Get Event](/api-reference/events/get) to inspect the complete stored event. Use [List Deliveries](/api-reference/webhooks/deliveries) for each webhook and join delivery `streamEventId` to event IDs. Do not use `x_event_id` as the delivery join key. Use [Update Monitor](/api-reference/monitors/update) to replace `eventTypes` or toggle `isActive`. Use [Delete Monitor](/api-reference/monitors/delete-twitter-account-monitor) only when the tracked account should stop permanently. ## Reconcile an Account Monitor Inventory Store one row per monitor ID. Include its username, X user ID, and event types. Add the active state and billing date. Join monitors to webhooks by configured ownership. Flag missing webhooks, extra webhooks, and mismatched event types. Compare completed snapshots to identify new, paused, resumed, or removed monitors. Do not compare partially paginated inventories. Use the single-monitor route for an incident review. Update only the monitors approved for change. ## Assign Account Monitor Ownership Use the complete list to assign every tracked X profile. Start with monitor ID, `username`, and stable `xUserId`. Keep the stable ID when a username changes. Group rows by the team consuming their events. Support teams may own reply and mention events. Research teams may own new posts. Account operations may own profile changes. Store ownership outside returned API fields. Compare `eventTypes` with each receiver contract. A webhook receiving only tweet events cannot process a profile biography change. Flag missing subscriptions before calling the monitor healthy. Then inspect active state. Active monitors can produce new account events. Paused monitors preserve configuration without polling. Keep both groups in the inventory. Never treat a paused row as deleted. Use `nextBillingAt` for active-budget reviews. Listing remains free. Count active account monitors separately from keyword monitors. Their targets and event contracts serve different workflows. Publish one ownership row per monitor ID. Include username, X user ID, event types, active state, and billing checkpoint. Add its owner and review date. This record makes later account-monitor changes explicit. ## Prepare Safe Bulk Account Monitor Cleanup Begin from one complete list response. Do not combine partial snapshots. Mark rows lacking an owner, current workflow, or required webhook subscription. Inspect recent stored events before proposing a pause. An empty window does not prove the profile is irrelevant. Confirm the account, event types, and review window with the owner. Pause monitors through the update route first. Keep their IDs and previous event types in the change record. Observe receiver queues before permanent deletion. Delete only approved rows. Re-list monitors after every batch. Confirm removed IDs are absent and paused IDs remain present. Record billing checkpoints for the remaining active rows. Handle uncertain changes individually. Use the single-monitor status route for each affected ID. Never replay a bulk delete because the client lost its local response. ## Headers Your API key. You can also sign in with a session cookie. Create a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Array of monitor objects. Unique monitor ID. Normalized X username. Resolved X user ID. Subscribed event types. Whether the monitor is currently active. ISO 8601 creation timestamp. Next hourly credit charge time for active monitor billing. Total number of monitors. ```json theme={null} { "monitors": [ { "id": "7", "username": "elonmusk", "xUserId": "44196397", "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T11:30:00.000Z" }, { "id": "12", "username": "xquik_", "xUserId": "1849726401547751424", "eventTypes": ["tweet.new", "tweet.quote", "tweet.reply", "tweet.retweet"], "isActive": true, "createdAt": "2026-02-25T14:00:00.000Z", "nextBillingAt": "2026-02-25T15:00:00.000Z" } ], "total": 2 } ``` ### 401 Missing API Key ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. Returns up to 200 monitors. There is no pagination. [Contact support](mailto:support@xquik.com) if you need more. **Related:** [Create Monitor](/api-reference/monitors/create) to add a new monitor, [Get Monitor](/api-reference/monitors/twitter-account-monitor-status) to fetch one monitor, [List Events](/api-reference/events/list) to audit stored events, [Get Event](/api-reference/events/get) to inspect one event, [List Webhooks](/api-reference/webhooks/list) to compare subscriptions, or [List Deliveries](/api-reference/webhooks/deliveries) to audit webhook delivery status. # Twitter Keyword Monitor List & Tracked Searches Source: https://docs.xquik.com/api-reference/monitors/list-keywords GET /monitors/keywords List keyword monitors with X search queries, matching tweet event types, active states, polling intervals, billing state, and timestamps. See event fields. ```json theme={null} { "monitors": [], "total": 0 } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Reconcile the Entire Keyword Monitor Portfolio Use this endpoint when the exact monitor ID is unknown. It returns up to 200 stored searches plus `total`. It does not paginate the inventory. | Portfolio check | Inventory calculation | Reconciliation decision | | ---------------------- | -------------------------------------- | -------------------------------------------------------- | | Active searches | Count `monitors[].isActive === true` | Include only active rows in current polling reviews. | | Paused searches | Count `monitors[].isActive === false` | Preserve them for history or later reactivation. | | Duplicate queries | Group by normalized `monitors[].query` | Compare `eventTypes` before treating rows as duplicates. | | Webhook gaps | Compare every `eventTypes` array | Add only missing receiver subscriptions. | | Billing order | Sort active `nextBillingAt` values | Review the nearest credit checkpoints first. | | Inventory completeness | Compare `monitors.length` with `total` | Escalate when the returned collection is incomplete. | Use the detail endpoint only after choosing one monitor ID from this inventory. **Free** - does not consume credits ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/monitors/keywords \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq -c '.monitors[] | { keyword_monitor_id: .id, query: .query, event_types: .eventTypes, is_active: .isActive, next_billing_at: .nextBillingAt, events_endpoint: "/api/v1/events?keywordMonitorId=\(.id)", event_detail_endpoint_pattern: "/api/v1/events/{event_id}", verify_endpoint: "/api/v1/monitors/keywords/\(.id)", update_endpoint: "/api/v1/monitors/keywords/\(.id)", delete_endpoint: "/api/v1/monitors/keywords/\(.id)", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/monitors/keywords", { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const payload = await response.json(); for (const monitor of payload.monitors) { const monitorRow = { keyword_monitor_id: monitor.id, query: monitor.query, event_types: monitor.eventTypes, is_active: monitor.isActive, created_at: monitor.createdAt, next_billing_at: monitor.nextBillingAt, events_endpoint: `/api/v1/events?keywordMonitorId=${monitor.id}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", verify_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, update_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, delete_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(monitorRow)}\n`); } ``` ```python Python theme={null} import json import requests response = requests.get( "https://xquik.com/api/v1/monitors/keywords", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) payload = response.json() for monitor in payload["monitors"]: monitor_row = { "keyword_monitor_id": monitor["id"], "query": monitor["query"], "event_types": monitor["eventTypes"], "is_active": monitor["isActive"], "created_at": monitor["createdAt"], "next_billing_at": monitor["nextBillingAt"], "events_endpoint": f"/api/v1/events?keywordMonitorId={monitor['id']}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "verify_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "update_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "delete_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(monitor_row)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type KeywordMonitor struct { ID string `json:"id"` Query string `json:"query"` EventTypes []string `json:"eventTypes"` IsActive bool `json:"isActive"` CreatedAt string `json:"createdAt"` NextBillingAt string `json:"nextBillingAt"` } type KeywordMonitorListResponse struct { Monitors []KeywordMonitor `json:"monitors"` Total int `json:"total"` } type KeywordMonitorRow struct { KeywordMonitorID string `json:"keyword_monitor_id"` Query string `json:"query"` EventTypes []string `json:"event_types"` IsActive bool `json:"is_active"` CreatedAt string `json:"created_at"` NextBillingAt string `json:"next_billing_at"` EventsEndpoint string `json:"events_endpoint"` EventDetailEndpointPattern string `json:"event_detail_endpoint_pattern"` VerifyEndpoint string `json:"verify_endpoint"` UpdateEndpoint string `json:"update_endpoint"` DeleteEndpoint string `json:"delete_endpoint"` WebhooksEndpoint string `json:"webhooks_endpoint"` DeliveriesEndpointPattern string `json:"deliveries_endpoint_pattern"` } func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/monitors/keywords", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var payload KeywordMonitorListResponse if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { log.Fatal(err) } encoder := json.NewEncoder(os.Stdout) for _, monitor := range payload.Monitors { row := KeywordMonitorRow{ KeywordMonitorID: monitor.ID, Query: monitor.Query, EventTypes: monitor.EventTypes, IsActive: monitor.IsActive, CreatedAt: monitor.CreatedAt, NextBillingAt: monitor.NextBillingAt, EventsEndpoint: "/api/v1/events?keywordMonitorId=" + monitor.ID, EventDetailEndpointPattern: "/api/v1/events/{event_id}", VerifyEndpoint: "/api/v1/monitors/keywords/" + monitor.ID, UpdateEndpoint: "/api/v1/monitors/keywords/" + monitor.ID, DeleteEndpoint: "/api/v1/monitors/keywords/" + monitor.ID, WebhooksEndpoint: "/api/v1/webhooks", DeliveriesEndpointPattern: "/api/v1/webhooks/{webhook_id}/deliveries", } if err := encoder.Encode(row); err != nil { log.Fatal(err) } } } ``` The cURL, Node.js, Python, and Go examples convert each keyword monitor into one inventory row. Store `keyword_monitor_id`, `query`, `event_types`, `is_active`, `next_billing_at`, `events_endpoint`, `event_detail_endpoint_pattern`, `verify_endpoint`, `update_endpoint`, `delete_endpoint`, `webhooks_endpoint`, and `deliveries_endpoint_pattern` before reconciling events, webhooks, or paused queries. ## Inventory handoff Use `GET /monitors/keywords` after create, update, pause, or delete operations to rebuild your keyword monitor inventory. The response returns up to 200 keyword monitors ordered by creation time and a `total` count for the returned set. | Keyword monitor inventory column | Response source | Reconciliation rule | | -------------------------------- | -------------------------- | ----------------------------------------------- | | Monitor ID | `monitors[].id` | Use this ID for status, updates, and events. | | X search query | `monitors[].query` | Preserve the exact stored keyword expression. | | Tweet event filter | `monitors[].eventTypes` | Compare these types with webhook subscriptions. | | Polling state | `monitors[].isActive` | Separate active and paused keyword monitors. | | Creation time | `monitors[].createdAt` | Track configuration age and drift. | | Billing checkpoint | `monitors[].nextBillingAt` | Schedule the next active monitor credit check. | | Inventory count | `total` | Compare this count with emitted monitor rows. | Filter monitors where `isActive` is `true`. Each active keyword monitor bills 21 credits per active monitor-hour; use `nextBillingAt` to schedule credit checks or pause stale alerts. Compare each monitor's `eventTypes` with [List Webhooks](/api-reference/webhooks/list) before relying on signed alerts. Use `id` as `keywordMonitorId` with [List Events](/api-reference/events/list) to audit stored monitor events. Use [Get Event](/api-reference/events/get) for one event's full payload. Use [List Deliveries](/api-reference/webhooks/deliveries) when webhook delivery evidence must be retained. Join delivery `streamEventId` to event IDs. Do not use `x_event_id` as the delivery join key. Use [Get Keyword Monitor](/api-reference/monitors/get-keyword) before changing a row. Store the returned `query`, `eventTypes`, `isActive`, and `nextBillingAt`. Use [Update Keyword Monitor](/api-reference/monitors/update-keyword) to replace `eventTypes` or toggle `isActive`. Use [Delete Keyword Monitor](/api-reference/monitors/delete-keyword) only when the query should stop permanently. ## Review a Portfolio of Tracked Search Rules Use the returned list to inspect every stored Twitter keyword query. Start with `total`, then count the emitted `monitors` rows. Store the snapshot time beside both values. A later comparison needs a complete inventory. Classify each query by its actual search intent. Separate brand handles, product names, campaign hashtags, support phrases, and competitor terms. Keep the original `query` text. Do not replace it with a dashboard label. Group identical normalized queries before changing anything. Two active rows can watch the same phrase with different event types. Compare `eventTypes` before calling them duplicates. Preserve intentional routing differences. Next, separate active and paused rows. Active rows can create new matching events. Paused rows remain useful for history and later reactivation. Never infer activity from an empty event window alone. Compare every active row with webhook subscriptions. Flag missing event types, unused webhook event types, and absent receiver ownership. Use the monitor ID to inspect stored events. Use event IDs to inspect delivery attempts. Finish with one inventory record per keyword monitor. Keep the monitor ID, query, event types, active state, creation time, and billing checkpoint. Add the responsible team and review date outside the API response. ## Prepare a Keyword Monitor Budget Report Count only active keyword monitors when estimating hourly monitor use. Each active monitor bills 21 credits per active monitor-hour. Listing the inventory remains free. Sort active rows by `nextBillingAt`. This reveals the next billing checkpoints without changing monitor state. Join each row with its owner and campaign end date. Pause expired campaigns through the update route. Keep paused monitors outside the active-burn total. Keep deleted monitors outside the current inventory. Preserve separate history when compliance or support needs older event evidence. Rebuild the report after every create, update, pause, resume, or delete. Compare complete snapshots by monitor ID. Report new, reactivated, paused, and removed queries separately. This prevents one aggregate count from hiding state changes. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Array of keyword monitor objects. Unique keyword monitor ID. Normalized X search query. Subscribed event types. Whether the monitor is currently active. ISO 8601 creation timestamp. Next hourly credit charge time for active monitor billing. Total number of keyword monitors. ```json theme={null} { "monitors": [ { "id": "21", "query": "xquik OR \"x api\"", "eventTypes": ["tweet.new"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T11:30:00.000Z" } ], "total": 1 } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. Returns up to 200 keyword monitors. There is no pagination. [Contact support](mailto:support@xquik.com) if you need more. **Related:** [Create Keyword Monitor](/api-reference/monitors/create-keyword) to add a query, [Get Keyword Monitor](/api-reference/monitors/get-keyword) to fetch one monitor, [Update Keyword Monitor](/api-reference/monitors/update-keyword) to pause or edit it, [Delete Keyword Monitor](/api-reference/monitors/delete-keyword) to remove it, [List Events](/api-reference/events/list) and [Get Event](/api-reference/events/get) to audit events, or [List Webhooks](/api-reference/webhooks/list) and [List Deliveries](/api-reference/webhooks/deliveries) to audit delivery evidence. # Twitter Account Activity Tracker & Monitor Status Source: https://docs.xquik.com/api-reference/monitors/twitter-account-monitor-status GET /monitors/{id} Check one Twitter account activity tracker for its X profile, tweet and profile event filters, active alert state, billing, events, and webhook deliveries. ```json theme={null} { "id": "42", "username": "elonmusk", "xUserId": "1234567890", "eventTypes": [ "tweet.new" ], "isActive": true, "createdAt": "2025-01-15T12:00:00Z", "nextBillingAt": "2025-01-15T13:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Check One Twitter Account Activity Tracker Use this route for one Twitter account activity tracker status check. It returns the tracked X profile, selected event types, active state, and billing timing. Use update to change event filters or pause the monitor. Use list for the complete monitor inventory. **Free** - does not consume credits ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/monitors/7 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '{ monitor_id: .id, username: .username, x_user_id: .xUserId, event_types: .eventTypes, is_active: .isActive, created_at: .createdAt, next_billing_at: .nextBillingAt, update_endpoint: ("/api/v1/monitors/" + .id), events_endpoint: ("/api/v1/events?monitorId=" + .id), event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const monitorId = "7"; const response = await fetch(`https://xquik.com/api/v1/monitors/${monitorId}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const monitor = await response.json(); const monitorState = { monitor_id: monitor.id, username: monitor.username, x_user_id: monitor.xUserId, event_types: monitor.eventTypes, is_active: monitor.isActive, created_at: monitor.createdAt, next_billing_at: monitor.nextBillingAt, update_endpoint: `/api/v1/monitors/${monitor.id}`, events_endpoint: `/api/v1/events?monitorId=${monitor.id}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(monitorState)}\n`); ``` ```python Python theme={null} import json import requests monitor_id = "7" response = requests.get( f"https://xquik.com/api/v1/monitors/{monitor_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) monitor = response.json() monitor_state = { "monitor_id": monitor["id"], "username": monitor["username"], "x_user_id": monitor["xUserId"], "event_types": monitor["eventTypes"], "is_active": monitor["isActive"], "created_at": monitor["createdAt"], "next_billing_at": monitor["nextBillingAt"], "update_endpoint": f"/api/v1/monitors/{monitor['id']}", "events_endpoint": f"/api/v1/events?monitorId={monitor['id']}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(monitor_state)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type Monitor struct { ID string `json:"id"` Username string `json:"username"` XUserID string `json:"xUserId"` EventTypes []string `json:"eventTypes"` IsActive bool `json:"isActive"` CreatedAt string `json:"createdAt"` NextBillingAt string `json:"nextBillingAt"` } type MonitorState struct { DeliveriesEndpointPattern string `json:"deliveries_endpoint_pattern"` EventDetailEndpointPattern string `json:"event_detail_endpoint_pattern"` EventsEndpoint string `json:"events_endpoint"` EventTypes []string `json:"event_types"` CreatedAt string `json:"created_at"` IsActive bool `json:"is_active"` MonitorID string `json:"monitor_id"` NextBillingAt string `json:"next_billing_at"` UpdateEndpoint string `json:"update_endpoint"` Username string `json:"username"` WebhooksEndpoint string `json:"webhooks_endpoint"` XUserID string `json:"x_user_id"` } func main() { monitorID := "7" req, err := http.NewRequest("GET", "https://xquik.com/api/v1/monitors/"+monitorID, nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var monitor Monitor if err := json.NewDecoder(resp.Body).Decode(&monitor); err != nil { log.Fatal(err) } state := MonitorState{ DeliveriesEndpointPattern: "/api/v1/webhooks/{webhook_id}/deliveries", EventDetailEndpointPattern: "/api/v1/events/{event_id}", EventsEndpoint: "/api/v1/events?monitorId=" + monitor.ID, EventTypes: monitor.EventTypes, CreatedAt: monitor.CreatedAt, IsActive: monitor.IsActive, MonitorID: monitor.ID, NextBillingAt: monitor.NextBillingAt, UpdateEndpoint: "/api/v1/monitors/" + monitor.ID, Username: monitor.Username, WebhooksEndpoint: "/api/v1/webhooks", XUserID: monitor.XUserID, } if err := json.NewEncoder(os.Stdout).Encode(state); err != nil { log.Fatal(err) } } ``` The Node.js, Python, and Go examples produce one normalized monitor snapshot. Store `monitor_id`, `event_types`, `is_active`, `next_billing_at`, `update_endpoint`, `events_endpoint`, `event_detail_endpoint_pattern`, `webhooks_endpoint`, and `deliveries_endpoint_pattern` before changing filters, pausing alerts, or reconciling webhooks. ## State Handoff Use `GET /monitors/{id}` before changing routing, billing checks, or alert state for one account monitor. The endpoint returns the current stored monitor for your account only; deleted or cross-account IDs return `404`. | Account monitor status column | Response source | Decision rule | | ----------------------------- | --------------- | --------------------------------------------------- | | Monitor ID | `id` | Use this ID for updates, events, and deletion. | | X username | `username` | Confirm the intended tracked profile. | | X user ID | `xUserId` | Use this stable ID for account verification. | | Event filter | `eventTypes` | Align webhook subscriptions before enabling alerts. | | Polling state | `isActive` | Decide whether checks and billing should continue. | | Creation time | `createdAt` | Record when the monitor was created. | | Billing checkpoint | `nextBillingAt` | Review credits before the next active charge. | Treat `username` and `xUserId` as the resolved X account identity. Store both with downstream CRM, warehouse, or queue records. Treat `eventTypes` as the active matching contract. Mirror those event types into webhook subscriptions before relying on signed alerts. Use `isActive` to decide whether the monitor should poll and bill. Use [Update Monitor](/api-reference/monitors/update) to pause or resume it. Use `id` as `monitorId` with [List Events](/api-reference/events/list) to reconcile stored events and webhook deliveries for this account. Use event IDs returned by [List Events](/api-reference/events/list) with [Get Event](/api-reference/events/get) when a support, audit, or agent workflow needs the full tweet payload. Use [List Webhooks](/api-reference/webhooks/list) to compare webhook `eventTypes` with this monitor before relying on signed alerts. Use [List Deliveries](/api-reference/webhooks/deliveries) for each webhook and join delivery `streamEventId` to event IDs. Do not use `x_event_id` as the delivery join key. ## What Does Twitter Account Activity Tracker Status Prove? The response proves which X profile one monitor targets. Confirm both `username` and `xUserId`. Keep the stable X user ID when a username changes. A stable X user ID preserves identity after username changes. Read `eventTypes` as the monitor's current matching contract. Tweet filters include new posts, replies, quotes, reposts, media, links, and polls. Additional filters detect mentions, hashtags, and long-form posts. Profile filters cover avatar, banner, name, username, biography, location, and URL. They also cover verification, protection, pinned tweets, and account availability. The response returns only the selected filters. Followers and following are not monitor event types. Account relationship changes are not monitor events either. For follower growth, compare complete [follower snapshots](/api-reference/x/followers) at approved intervals. Use the [follower export guide](/guides/follower-export-crm) to create controlled follower CSV snapshots. Read `isActive` as the polling state. Active monitors capture selected future account changes. Pausing retains the profile and filters while disabling future monitoring. Read `nextBillingAt` as the next scheduled charge. It does not prove that an event or webhook delivery succeeded. ## How Do I Check Whether Twitter Account Activity Alerts Are Active? Check the monitor before diagnosing a missing alert. A healthy account alert requires four aligned layers. | Alert layer | Status check | Required evidence | | --------------- | ----------------------------- | ------------------------------------------------------------- | | Tracked profile | `username` and `xUserId` | Both identify the intended X account. | | Event filter | `eventTypes` | The expected tweet or profile event is selected. | | Polling | `isActive` | The monitor was active during the expected change. | | Delivery | Events and webhook deliveries | A stored event exists and the receiver accepted its delivery. | First, confirm the tracked profile. Next, find the expected type in `eventTypes`. Then verify `isActive`. Query [List Events](/api-reference/events/list) with this monitor ID. Finally, join the stored event to [webhook deliveries](/api-reference/webhooks/deliveries) through `streamEventId`. Do not treat one active flag as complete health evidence. The monitor may poll correctly while a webhook fails. A webhook may also work while the monitor omits the expected event type. Keep configuration, event, and delivery checks independent during diagnosis. ## How Do I Track Mentions and Replies for One Account? Confirm that `eventTypes` contains `tweet.mention` for mentions. For reply alerts, require the `tweet.reply` event type. Configure either event type independently or enable both together. The status route shows the stored selection. It does not return the matching tweets. Use [List Events](/api-reference/events/list) with `monitorId` after confirming the filters. Filter stored events by the required type and time window. Open the complete record through [Get Event](/api-reference/events/get). The response contains the matched tweet payload. Add any missing filter before monitoring future alerts. Resume a paused monitor when the filter already exists. If both checks pass, trace the stored event and its webhook delivery. Never infer missing tweet activity from one failed receiver attempt. ## Does This Endpoint Return Account Analytics or Historical Reports? No. This endpoint returns one monitor configuration. It does not calculate engagement rate, follower growth, posting frequency, reach, impressions, or sentiment. It also does not backfill a native X analytics report. Monitor event history begins after monitor creation. [List Events](/api-reference/events/list) returns cursor-based pages of stored event records. Call [X user tweets](/api-reference/x/user-tweets) when an audit requires recent profile posts. Use an extraction job for follower CSV exports. Keep these outputs distinct. A monitor status snapshot explains configuration. An event record proves one matched change. A delivery record proves one webhook delivery attempt. A follower export records a dated follower list. None of those outputs is an engagement analytics dashboard. ## How Do Twitter Analytics Tools Differ From Monitor Status? Twitter analytics tools usually calculate performance from posts and account metrics. They may summarize likes, replies, reposts, views, or follower changes. Those values are engagement metrics. This endpoint reports the saved profile, filters, and polling status. The response describes one saved monitor. It identifies the tracked account and selected event filters. It also reports whether monitoring continues. The response never calculates reach, impressions, engagement rates, or audience growth. Check monitor status before investigating account activity. First, confirm the stable `xUserId`. Then inspect `eventTypes` and `isActive`. These fields prove whether Xquik could capture the expected change. They do not prove that the change occurred. Use [List Events](/api-reference/events/list) to find changes captured by the monitor. Open each event before making an activity claim. Use [webhook deliveries](/api-reference/webhooks/deliveries) to verify receiver attempts. This workflow separates monitor configuration from analytics reporting. Do not compare an empty event list with a complete analytics report. Event history begins when the monitor captures a selected change. Paused monitors do not capture additional account events. Omitted filters cannot produce corresponding event records. Record both conditions before drawing activity conclusions. ## What Should a Twitter Account Activity Audit Store? Start an audit with the monitor response. Store `id`, `username`, and `xUserId`. Add `eventTypes`, `isActive`, `createdAt`, and `nextBillingAt`. Together, these fields form one reproducible monitor snapshot. Record the request timestamp beside the response. The endpoint returns monitor creation time, not the audit observation time. Keep the HTTP status and monitor ID with that entry. Record the intended event beside the stored filters. For a reply alert, record the `tweet.reply` type. For a profile-name change, record `profile.name`. This comparison shows whether the required filter existed during the review. When an event exists, store its event ID and `monitorId`. Open the event and confirm its type. Then connect delivery `streamEventId` values to that event ID. Store each `deliveryId` and delivery status. This chain distinguishes a missing event from a failed receiver. Repeat the status request after each approved update. Compare the account, filters, polling status, and next scheduled charge. Preserve both snapshots with the approved change ticket. Reject the update when `xUserId` unexpectedly changes. Treat `404` as an ownership or existence failure. Never infer previous monitor states from that response. Before continuing, confirm the authenticated account and monitor ID. Treat `429` as a temporary request limit. Read the response `Retry-After` header and pause for the supplied duration before retrying. Do not change the audit conclusion. ## Approve One Account Monitor Change Fetch the monitor immediately before an approved update. Store its monitor ID, username, and stable X user ID. Add event types, active state, and billing schedule. This snapshot becomes the change baseline. Confirm the target profile first. Usernames can change. The stable `xUserId` prevents a renamed profile from looking like another account. Stop when the requested profile does not match the stored target. Review existing and proposed `eventTypes` together. List every added and removed account event. Check whether existing webhooks accept the proposed types. Update webhook subscriptions before relying on new alerts. Read `isActive` separately from event configuration. A pause keeps the target and event types. A resume restores polling under the stored contract. Record the previous state before toggling it. After the update, fetch the same monitor ID again. Compare its target, event types, active state, and `nextBillingAt`. Keep both snapshots with the approved change ticket. This proves the exact account-monitor change. ## Trace One Missing Profile Alert Start with the monitor ID from the affected workflow. Confirm its username and stable X user ID. Then verify the expected event type exists. Check `isActive` during the missing-alert window. A paused monitor does not create future account events. Check `nextBillingAt` and available credits when monitoring should continue. Query stored events with `monitorId`. Separate an absent event from an absent webhook delivery. Open the matching event before inspecting receiver attempts. For delivery review, join the event ID with delivery `streamEventId`. Keep `deliveryId` as the attempt identity. Do not join through an X Tweet ID. Record one diagnosis outcome. The monitor may target the wrong profile. It may omit the event type or remain paused. It may lack a stored event. The webhook delivery may fail. Those outcomes require different fixes. ## Path parameters The unique monitor ID. Returned when you [create a monitor](/api-reference/monitors/create) or [list monitors](/api-reference/monitors/list). ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Unique monitor ID. Normalized X username. Resolved X user ID. Subscribed event types. Indicates whether monitoring continues. Creation time in ISO 8601 format. Next hourly credit charge time for active monitor billing. ```json theme={null} { "id": "7", "username": "elonmusk", "xUserId": "44196397", "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T11:30:00.000Z" } ``` ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided monitor ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Monitor not found" } ``` No monitor exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [List Monitors](/api-reference/monitors/list) to see all monitors, [List Events](/api-reference/events/list) to audit stored events, [List Deliveries](/api-reference/webhooks/deliveries) to audit webhook delivery status, [Update Monitor](/api-reference/monitors/update) to change event types or toggle active status, or [Delete Monitor](/api-reference/monitors/delete-twitter-account-monitor) to remove this monitor. # Twitter Monitoring Alerts: How to Set Twitter Alerts Source: https://docs.xquik.com/api-reference/monitors/update PATCH /monitors/{id} Update Twitter account alert settings by replacing tweet and profile filters, pausing or resuming monitoring, and safely handling unavailable X profiles. ```json theme={null} { "id": "42", "username": "elonmusk", "xUserId": "1234567890", "eventTypes": [ "tweet.new" ], "isActive": true, "createdAt": "2025-01-15T12:00:00Z", "nextBillingAt": "2025-01-15T13:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "monitor_profile_unavailable", "message": "X account unavailable. Restore it before resuming." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Learn how to set Twitter alerts for one saved X profile. This Twitter monitoring tool replaces tweet and profile filters. It can also pause or resume the same monitor. The response shows the updated account and event filter. This route cannot change the profile, keyword query, webhook URL, alert threshold, or sentiment rule. Manage webhook delivery separately. ```bash cURL theme={null} curl -X PATCH https://xquik.com/api/v1/monitors/7 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true }' | jq -c '{ monitor_id: .id, username, x_user_id: .xUserId, event_types: .eventTypes, is_active: .isActive, created_at: .createdAt, next_billing_at: .nextBillingAt, verify_endpoint: ("/api/v1/monitors/" + .id), list_endpoint: "/api/v1/monitors", events_endpoint: ("/api/v1/events?monitorId=" + .id), event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const monitorId = "7"; const response = await fetch(`https://xquik.com/api/v1/monitors/${monitorId}`, { method: "PATCH", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ eventTypes: ["tweet.new", "tweet.reply"], isActive: true, }), }); const monitor = await response.json(); const monitorState = { monitor_id: monitor.id, username: monitor.username, x_user_id: monitor.xUserId, event_types: monitor.eventTypes, is_active: monitor.isActive, created_at: monitor.createdAt, next_billing_at: monitor.nextBillingAt, verify_endpoint: `/api/v1/monitors/${monitor.id}`, list_endpoint: "/api/v1/monitors", events_endpoint: `/api/v1/events?monitorId=${monitor.id}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(monitorState)}\n`); ``` ```python Python theme={null} import json import requests monitor_id = "7" response = requests.patch( f"https://xquik.com/api/v1/monitors/{monitor_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "eventTypes": ["tweet.new", "tweet.reply"], "isActive": True, }, ) monitor = response.json() monitor_state = { "monitor_id": monitor["id"], "username": monitor["username"], "x_user_id": monitor["xUserId"], "event_types": monitor["eventTypes"], "is_active": monitor["isActive"], "created_at": monitor["createdAt"], "next_billing_at": monitor["nextBillingAt"], "verify_endpoint": f"/api/v1/monitors/{monitor['id']}", "list_endpoint": "/api/v1/monitors", "events_endpoint": f"/api/v1/events?monitorId={monitor['id']}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(monitor_state)) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "log" "net/http" "os" ) type Monitor struct { ID string `json:"id"` Username string `json:"username"` XUserID string `json:"xUserId"` EventTypes []string `json:"eventTypes"` IsActive bool `json:"isActive"` CreatedAt string `json:"createdAt"` NextBillingAt string `json:"nextBillingAt"` } type MonitorState struct { DeliveriesEndpointPattern string `json:"deliveries_endpoint_pattern"` EventDetailEndpointPattern string `json:"event_detail_endpoint_pattern"` EventsEndpoint string `json:"events_endpoint"` EventTypes []string `json:"event_types"` CreatedAt string `json:"created_at"` IsActive bool `json:"is_active"` ListEndpoint string `json:"list_endpoint"` MonitorID string `json:"monitor_id"` NextBillingAt string `json:"next_billing_at"` Username string `json:"username"` VerifyEndpoint string `json:"verify_endpoint"` WebhooksEndpoint string `json:"webhooks_endpoint"` XUserID string `json:"x_user_id"` } func main() { body, _ := json.Marshal(map[string]interface{}{ "eventTypes": []string{"tweet.new", "tweet.reply"}, "isActive": true, }) monitorID := "7" req, err := http.NewRequest("PATCH", "https://xquik.com/api/v1/monitors/"+monitorID, bytes.NewReader(body)) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var monitor Monitor if err := json.NewDecoder(resp.Body).Decode(&monitor); err != nil { log.Fatal(err) } state := MonitorState{ DeliveriesEndpointPattern: "/api/v1/webhooks/{webhook_id}/deliveries", EventDetailEndpointPattern: "/api/v1/events/{event_id}", EventsEndpoint: "/api/v1/events?monitorId=" + monitor.ID, EventTypes: monitor.EventTypes, CreatedAt: monitor.CreatedAt, IsActive: monitor.IsActive, ListEndpoint: "/api/v1/monitors", MonitorID: monitor.ID, NextBillingAt: monitor.NextBillingAt, Username: monitor.Username, VerifyEndpoint: "/api/v1/monitors/" + monitor.ID, WebhooksEndpoint: "/api/v1/webhooks", XUserID: monitor.XUserID, } if err := json.NewEncoder(os.Stdout).Encode(state); err != nil { log.Fatal(err) } } ``` The Node.js, Python, and Go examples print one reusable monitor record. Store `monitor_id`, `event_types`, `is_active`, `next_billing_at`, `verify_endpoint`, `list_endpoint`, `events_endpoint`, `event_detail_endpoint_pattern`, `webhooks_endpoint`, and `deliveries_endpoint_pattern` before resuming alerts or webhook checks. ## Path parameters The unique monitor ID. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body At least 1 field is required. Updated array of event types. Must contain at least 1 valid account monitor type: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, `tweet.longform`, `profile.avatar.changed`, `profile.banner.changed`, `profile.name.changed`, `profile.username.changed`, `profile.bio.changed`, `profile.location.changed`, `profile.url.changed`, `profile.verified.changed`, `profile.protected.changed`, `profile.pinned_tweet.changed`, `profile.unavailable.changed`. Set to `false` to pause monitoring, or `true` to resume. Paused account monitors do not consume hourly monitor credits. ## How to Set Twitter Alerts for an Existing Account Monitor Read the monitor before changing its Twitter alert settings. Save its `id`, `username`, `xUserId`, `eventTypes`, `isActive`, and `nextBillingAt`. Send the complete `eventTypes` array when coverage changes. The new array replaces every existing event type. Omit it when only `isActive` changes. Set `false` to pause monitoring. Set `true` to resume monitoring. If the profile is unavailable, the API returns `409 monitor_profile_unavailable`. Wait for profile recovery, then retry. Finally, compare the returned account, filter, and active state with the approved request. ## Which Twitter Alert Settings Can This Endpoint Change? | Alert setting | PATCH behavior | Correct route when PATCH does not apply | | ---------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | Tweet and profile event filters | Replace `eventTypes` with the complete desired array. | Use this route. | | Monitoring state | Set `isActive` to pause or resume checks. | Use this route. | | Tracked X profile | Cannot change `username` or `xUserId`. | Delete this monitor and [create another account monitor](/api-reference/monitors/create). | | Keyword, hashtag, or product query | Cannot change an account monitor into a keyword monitor. | Create or [update a keyword monitor](/api-reference/monitors/update-keyword). | | Webhook destination | Cannot change an HTTPS receiver or delivery state. | [Update the webhook](/api-reference/webhooks/update). | | Delivery channels | Cannot send Slack, CRM, email, or queue messages directly. | Process a signed webhook in your integration. | | Volume or sentiment rules | This route has no threshold or sentiment field. | Evaluate stored events in your workflow. | Account filters select future tweet and profile events. Webhook subscriptions select events for an HTTPS receiver. Your receiver can notify Slack, update a CRM record, or add work to a queue. ## How Do Twitter Account Alert Settings Work in This Twitter Monitoring Tool? Twitter account alert settings choose captured tweet and profile changes. Authenticate with an API key or bearer token. To receive notifications, subscribe a webhook to the same event types. Webhook notification settings choose the HTTPS receiver. They do not change the Twitter account or event filter. Use keyword alerts for brand mentions, hashtags, products, or campaign phrases. Manage keyword monitoring through [Update Keyword Monitor](/api-reference/monitors/update-keyword). ## How Do I Send Twitter Account Alerts to Slack or a CRM? Update the account filter, then compare it with [List Webhooks](/api-reference/webhooks/list). Subscribe the receiver to every required event type. Run [Test Webhook](/api-reference/webhooks/test) before waiting for account activity. Validate each signature and acknowledge valid requests quickly. Process slower Slack or CRM work after acknowledgment. [List Events](/api-reference/events/list) confirms event storage. [List Deliveries](/api-reference/webhooks/deliveries) confirms delivery attempts. ## How Do I Reduce High-Volume Twitter Alert Noise? Select only required tweet and profile events. Select mention and reply events when those interactions matter. Add repost, quote, media, or link events only when your workflow uses those payloads. Use a keyword monitor for words, hashtags, products, or campaigns. Account monitor event types do not express Boolean search rules, languages, sensitivity, or sentiment. Keep webhook subscriptions aligned. Join delivery attempts to stored events with `streamEventId`. ## Update handoff Use this endpoint to change event scope. You can also pause or resume an account alert without creating another monitor ID. | Account monitor update column | Request or response source | Verification rule | | ----------------------------- | ---------------------------------------- | ------------------------------------------- | | Monitor ID | Path `{id}` and response `id` | Require both IDs to match. | | Event filter | `eventTypes` in the request and response | Match every event type. | | Polling state | `isActive` in the request and response | Match the pause or resume choice. | | X username | Response `username` | Confirm the tracked profile did not change. | | X user ID | Response `xUserId` | Preserve the stable account join. | | Webhook alignment | `GET /webhooks` | Match subscriptions before trusting alerts. | Store returned `id`, `username`, `xUserId`, `eventTypes`, `isActive`, `createdAt`, and `nextBillingAt` as the current account monitor configuration. Refresh [List Monitors](/api-reference/monitors/list) after the PATCH and compare [Get Monitor](/api-reference/monitors/twitter-account-monitor-status) for the same `id` before updating queues, CRM records, or support notes. `eventTypes` replaces the current filter. Keep [List Webhooks](/api-reference/webhooks/list) subscriptions aligned with the monitor event types you expect to deliver. `isActive: false` pauses future account checks, stored events, future webhook deliveries, and hourly monitor billing for this monitor. `isActive: true` resumes checks for matching future account activity. Check `nextBillingAt`, then run [Test Webhook](/api-reference/webhooks/test) before relying on production alerts. PATCH cannot change `username` or `xUserId`. Delete this monitor and create a new account monitor when the tracked account changes. Keep using `monitorId` and `username` from [List Events](/api-reference/events/list) to reconcile stored events after the update. Use returned event IDs with [Get Event](/api-reference/events/get) when a workflow needs the full tweet payload. Use [List Deliveries](/api-reference/webhooks/deliveries) for each webhook and join delivery `streamEventId` to event IDs. Do not use `x_event_id` as the delivery join key. ## Change One Account Monitor Safely Read the current monitor first. Send every approved `eventTypes` value. Use `isActive: false` for a temporary stop. Check credits before sending `true`. Then verify `id`, `username`, `xUserId`, `eventTypes`, and `nextBillingAt`. PATCH changes future monitoring only. It never rewrites stored events or past deliveries. Create another monitor when the tracked profile changes. ## Prepare a Reversible Account Monitor Change Record the current ID, username, events, active state, and billing time. Add the approved future values beside them. Send only supported PATCH fields. If verification fails, read the monitor again. Do not send an automatic rollback. Another approved update may already exist. ## Separate Pausing, Filtering, and Deletion Pause when the monitor may resume. Replace `eventTypes` to change future scope. Delete only for permanent removal. Create another monitor for another profile. This does not change past account events. ## Verify the First Event After Resuming Read the monitor and confirm `isActive: true`. Then inspect the first matching event's type and timestamp. Check deliveries separately. A successful PATCH is not delivery proof. ## Response ### 200 OK Unique monitor ID. Normalized X username. Resolved X user ID. Updated event types. Current active status after update. ISO 8601 creation timestamp. Next hourly credit charge time for active monitor billing. ```json theme={null} { "id": "7", "username": "elonmusk", "xUserId": "44196397", "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T11:30:00.000Z" } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Empty body, invalid event types, or invalid isActive type" } ``` Empty body, invalid `eventTypes` values, or invalid `isActive` type. ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided monitor ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Monitor not found" } ``` No monitor exists with this ID, or it belongs to a different account. ### 409 Profile Unavailable ```json theme={null} { "error": "monitor_profile_unavailable", "message": "X account unavailable. Restore it before resuming." } ``` The X profile is unavailable. Wait for profile recovery, then resume the monitor. You can still pause it or change its event filters. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. `isActive: false` pauses future checks, stored events, webhook deliveries, and hourly monitor billing for this monitor. `isActive: true` resumes future checks with the returned `eventTypes`. **Related:** [List Monitors](/api-reference/monitors/list) to refresh inventory, [Get Monitor](/api-reference/monitors/twitter-account-monitor-status) to verify this monitor, [List Events](/api-reference/events/list) to audit stored events, [Get Event](/api-reference/events/get) to inspect one event, [List Webhooks](/api-reference/webhooks/list) to compare subscriptions, or [List Deliveries](/api-reference/webhooks/deliveries) to audit webhook delivery status. # Twitter Keyword Monitor Updates & Alert Controls Source: https://docs.xquik.com/api-reference/monitors/update-keyword PATCH /monitors/keywords/{id} Update a keyword monitor's matching tweet event types, polling state, and active status without recreating its X search query. Includes request fields. ```json theme={null} { "id": "21", "query": "xquik OR \"x api\"", "eventTypes": [ "tweet.new" ], "isActive": true, "createdAt": "2025-01-15T12:00:00Z", "nextBillingAt": "2025-01-15T13:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Choose Keyword Monitor Updates Use this route to change one keyword monitor's event types or active state. Use get for a read-only checkpoint. Use list for account-wide keyword monitor inventory. **Free** - does not consume credits ```bash cURL theme={null} curl -X PATCH https://xquik.com/api/v1/monitors/keywords/21 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true }' | jq -c '{ keyword_monitor_id: .id, query: .query, event_types: .eventTypes, is_active: .isActive, created_at: .createdAt, next_billing_at: .nextBillingAt, verify_endpoint: "/api/v1/monitors/keywords/\(.id)", delete_endpoint: "/api/v1/monitors/keywords/\(.id)", events_endpoint: "/api/v1/events?keywordMonitorId=\(.id)", event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries" }' ``` ```javascript Node.js theme={null} const monitorId = "21"; const response = await fetch( `https://xquik.com/api/v1/monitors/keywords/${monitorId}`, { method: "PATCH", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ eventTypes: ["tweet.new", "tweet.reply"], isActive: true, }), }, ); const monitor = await response.json(); const monitorState = { keyword_monitor_id: monitor.id, query: monitor.query, event_types: monitor.eventTypes, is_active: monitor.isActive, created_at: monitor.createdAt, next_billing_at: monitor.nextBillingAt, verify_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, delete_endpoint: `/api/v1/monitors/keywords/${monitor.id}`, events_endpoint: `/api/v1/events?keywordMonitorId=${monitor.id}`, event_detail_endpoint_pattern: "/api/v1/events/{event_id}", webhooks_endpoint: "/api/v1/webhooks", deliveries_endpoint_pattern: "/api/v1/webhooks/{webhook_id}/deliveries", }; process.stdout.write(`${JSON.stringify(monitorState)}\n`); ``` ```python Python theme={null} import json import requests monitor_id = "21" response = requests.patch( f"https://xquik.com/api/v1/monitors/keywords/{monitor_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"eventTypes": ["tweet.new", "tweet.reply"], "isActive": True}, ) monitor = response.json() monitor_state = { "keyword_monitor_id": monitor["id"], "query": monitor["query"], "event_types": monitor["eventTypes"], "is_active": monitor["isActive"], "created_at": monitor["createdAt"], "next_billing_at": monitor["nextBillingAt"], "verify_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "delete_endpoint": f"/api/v1/monitors/keywords/{monitor['id']}", "events_endpoint": f"/api/v1/events?keywordMonitorId={monitor['id']}", "event_detail_endpoint_pattern": "/api/v1/events/{event_id}", "webhooks_endpoint": "/api/v1/webhooks", "deliveries_endpoint_pattern": "/api/v1/webhooks/{webhook_id}/deliveries", } print(json.dumps(monitor_state)) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "log" "net/http" "os" ) type KeywordMonitor struct { ID string `json:"id"` Query string `json:"query"` EventTypes []string `json:"eventTypes"` IsActive bool `json:"isActive"` CreatedAt string `json:"createdAt"` NextBillingAt string `json:"nextBillingAt"` } type KeywordMonitorState struct { KeywordMonitorID string `json:"keyword_monitor_id"` Query string `json:"query"` EventTypes []string `json:"event_types"` IsActive bool `json:"is_active"` CreatedAt string `json:"created_at"` NextBillingAt string `json:"next_billing_at"` VerifyEndpoint string `json:"verify_endpoint"` DeleteEndpoint string `json:"delete_endpoint"` EventsEndpoint string `json:"events_endpoint"` EventDetailEndpointPattern string `json:"event_detail_endpoint_pattern"` WebhooksEndpoint string `json:"webhooks_endpoint"` DeliveriesEndpointPattern string `json:"deliveries_endpoint_pattern"` } func main() { body, _ := json.Marshal(map[string]interface{}{ "eventTypes": []string{"tweet.new", "tweet.reply"}, "isActive": true, }) monitorID := "21" req, err := http.NewRequest( "PATCH", "https://xquik.com/api/v1/monitors/keywords/"+monitorID, bytes.NewReader(body), ) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var monitor KeywordMonitor if err := json.NewDecoder(resp.Body).Decode(&monitor); err != nil { log.Fatal(err) } state := KeywordMonitorState{ KeywordMonitorID: monitor.ID, Query: monitor.Query, EventTypes: monitor.EventTypes, IsActive: monitor.IsActive, CreatedAt: monitor.CreatedAt, NextBillingAt: monitor.NextBillingAt, VerifyEndpoint: "/api/v1/monitors/keywords/" + monitor.ID, DeleteEndpoint: "/api/v1/monitors/keywords/" + monitor.ID, EventsEndpoint: "/api/v1/events?keywordMonitorId=" + monitor.ID, EventDetailEndpointPattern: "/api/v1/events/{event_id}", WebhooksEndpoint: "/api/v1/webhooks", DeliveriesEndpointPattern: "/api/v1/webhooks/{webhook_id}/deliveries", } if err := json.NewEncoder(os.Stdout).Encode(state); err != nil { log.Fatal(err) } } ``` The cURL, Node.js, Python, and Go examples convert the updated keyword monitor into one state row. Store `keyword_monitor_id`, `query`, `event_types`, `is_active`, `next_billing_at`, `verify_endpoint`, `delete_endpoint`, `events_endpoint`, `event_detail_endpoint_pattern`, `webhooks_endpoint`, and `deliveries_endpoint_pattern` before resuming alerts or webhook checks. ## Path parameters The unique keyword monitor ID. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body At least 1 field is required. Updated array of event types. Must contain at least 1 valid keyword monitor type: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, `tweet.longform`. Set to `false` to pause monitoring, or `true` to resume. Paused keyword monitors do not consume hourly monitor credits. The monitored `query` is immutable. Delete this monitor and create another one to track a different query. ## Update handoff Use this endpoint when a keyword alert changes scope, needs a temporary pause, or must resume without creating a new monitor ID. | Keyword monitor update column | Request or response source | Verification rule | | ----------------------------- | --------------------------------- | ---------------------------------------------- | | Monitor ID | Path `{id}` and response `id` | Require both IDs to match. | | X search query | Response `query` | Confirm the immutable query stayed unchanged. | | Tweet event filter | Request and response `eventTypes` | Confirm the complete replacement filter. | | Polling state | Request and response `isActive` | Confirm the intended pause or resume state. | | Creation time | Response `createdAt` | Preserve the original configuration timestamp. | | Billing checkpoint | Response `nextBillingAt` | Recalculate the next credit review. | | Webhook alignment | `GET /webhooks` | Match subscriptions before trusting alerts. | Store returned `id`, `query`, `eventTypes`, `isActive`, `createdAt`, and `nextBillingAt` as the current keyword monitor configuration. `eventTypes` replaces the current filter. Keep [List Webhooks](/api-reference/webhooks/list) subscriptions aligned with the monitor event types you expect to deliver. `isActive: false` pauses keyword polling, stored events, future webhook deliveries, and hourly monitor billing for this monitor. `isActive: true` resumes polling for matching future tweets. Check `nextBillingAt`, then run [Test Webhook](/api-reference/webhooks/test) before relying on production alerts. PATCH cannot change `query`. Delete this monitor and create a new keyword monitor when the X search query changes. Keep using `keywordMonitorId` and `query` from [List Events](/api-reference/events/list) to reconcile stored events and signed webhook payloads after the update. Use [Get Event](/api-reference/events/get) for one event's full payload. Use [List Deliveries](/api-reference/webhooks/deliveries) when webhook delivery evidence must be retained. Join delivery `streamEventId` to event IDs. Do not use `x_event_id` as the delivery join key. Use [Delete Keyword Monitor](/api-reference/monitors/delete-keyword) only when the query should stop permanently. Export event and delivery evidence first when support or audit workflows need history. ## Response ### 200 OK Unique keyword monitor ID. Normalized X search query. Updated event types. Current active status after update. ISO 8601 creation timestamp. Next hourly credit charge time for active monitor billing. ```json theme={null} { "id": "21", "query": "xquik api", "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T11:30:00.000Z" } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Empty body, invalid event types, or invalid isActive type" } ``` Empty body, invalid `eventTypes` values, or invalid `isActive` type. ```json theme={null} { "error": "invalid_id", "message": "Invalid ID format." } ``` The provided monitor ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Monitor not found" } ``` No keyword monitor exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [Get Keyword Monitor](/api-reference/monitors/get-keyword) to verify state, [Delete Keyword Monitor](/api-reference/monitors/delete-keyword) to remove it, [List Events](/api-reference/events/list), [Get Event](/api-reference/events/get), [List Webhooks](/api-reference/webhooks/list), or [List Deliveries](/api-reference/webhooks/deliveries). # Twitter Scraper API & X API Alternative Guide Source: https://docs.xquik.com/api-reference/overview Use Xquik as a Twitter API alternative. Search tweets, export followers and replies, retrieve profiles, monitor accounts, post tweets, and send webhooks.
For the complete documentation index, see llms.txt.
Use Xquik as a Twitter API alternative for public reads and automated actions. Use it as an X API alternative across REST, SDKs, webhooks, and MCP. These Twitter API docs cover authentication, public reads, automated writes, and webhooks. Use this Twitter API documentation as a practical Twitter API for developers. It covers tweets, profiles, followers, replies, monitors, and media. Search tweets with the Twitter search API. Export user tweets, replies, followers, following, user profiles, timelines, communities, lists, and media. Post tweets and monitor accounts in real time. Deliver signed webhook events from the same API. Use the tweet scraping API to search recent and top tweets. Use the follower scraping API to export follower and following profiles. Use tweet reply scraping to retrieve conversations and authors. Use the Twitter webhook API to receive signed monitor events. Public tweet, profile, follower, reply, timeline, community, and list reads need no connected X account. Every X write requires one. Private reads, including DMs and bookmarks, also require one. See [Connect X account](/api-reference/x-accounts/connect). ## Choose a Twitter API Workflow Start with the workflow that matches your search intent: * [Search recent or top tweets](/api-reference/x/search-tweets) by query. * [Export tweet replies](/api-reference/x/tweet-replies) with author profiles. * [Export followers](/api-reference/x/followers) with pagination cursors. * [Export following profiles](/api-reference/x/following) for one account. * [Retrieve profiles](/api-reference/x/twitter-profile-lookup) by username or user ID. * [Create account monitors](/api-reference/monitors/create) for new tweets and profile changes. * [Create keyword monitors](/api-reference/monitors/create-keyword) for matching tweets. * [Send Twitter webhook events](/api-reference/webhooks/create) to your HTTPS endpoint. Use the Twitter scraper API for tweet search and brand monitoring. Retrieve tweets, profiles, timelines, communities, lists, and media as structured JSON. The follower scraper exports follower and following profiles. The reply scraper returns replies and authors. Webhooks deliver new-tweet and profile-change events. Create account and keyword monitors, retrieve events, manage webhooks Run tweet giveaway draws; extract tweets, replies, profiles, and followers Search tweets, retrieve profiles, export followers, and download media Post tweets, like, retweet, follow, DM, profile updates Manage credits, API keys, drafts, styles, and subscriptions Prepaid guest keys for 33 eligible GET routes
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
## Popular Twitter Scraper API Tasks Quote credits before scraping tweets, followers, replies, profiles, or timelines. List past tweet, follower, reply, profile, and timeline extraction jobs. Inspect one Twitter account monitor and its latest event activity. Delete an account monitor and stop its tweet and profile alerts. List past tweet giveaway draws, winners, eligibility rules, and rerolls. Create a hosted checkout for API access and recurring credits. ## Twitter Community & Audience Scraping Read a community's rules, member count, creator, moderators, and join policy. Retrieve community member profiles, usernames, verification, and follower counts. Retrieve moderator profiles, bios, verification, followers, and following. Export recent community tweets, authors, replies, reposts, likes, and media. Filter one community's tweets by keyword, recency, or top results. Retrieve profiles for every account one Twitter user follows. Retrieve profiles for accounts that retweeted or reposted one tweet. ## Base URL ```text theme={null} https://xquik.com/api/v1 ``` All endpoints are served over **HTTPS only**. Always use the full HTTPS base URL. ## OpenAPI spec The full API specification is available as an OpenAPI 3.1 document: ```text theme={null} https://xquik.com/openapi.json ``` YAML is also available at: ```text theme={null} https://docs.xquik.com/openapi.yaml ``` Use either format with OpenAPI tools or client generators. Xquik publishes the spec through [RFC 9727](https://www.rfc-editor.org/rfc/rfc9727) service discovery. API service discovery is published at: ```text theme={null} https://xquik.com/.well-known/api-catalog ``` ## Run in Postman Fork the official Xquik collection into your Postman workspace. It includes safe starter requests for tweets, users, timelines, and trends. [![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/12709954-5ee4fae0-138a-4615-ae47-ac4be7904df2?action=collection%2Ffork\&source=rip_markdown) [View the hosted collection documentation](https://documenter.getpostman.com/view/12709954/2sBY4TpxXK). ## Authentication Account API keys can use `x-api-key`: ```text theme={null} x-api-key: xq_your_api_key_here ``` Generate account keys from the [API Keys dashboard](https://dashboard.xquik.com/en/account?tab=api-keys). Xquik API keys also support `Authorization: Bearer xq_your_api_key_here`. OAuth 2.1 Bearer tokens keep their granted account scopes. Accountless guest keys use `Authorization: Bearer` with the fixed `paid_reads` scope on [33 eligible routes](/guides/guest-wallets#eligible-paid-read-routes). A verified payment activates each key. See [Authentication](/api-reference/authentication). ### Machine Payments Protocol Seven fixed-price GET operations accept anonymous [MPP](/mpp/machine-payments-protocol) payments. The server returns `402` with a `WWW-Authenticate: Payment` challenge. The response can include a guest wallet action. Complete the MPP challenge or request explicit wallet confirmation. A failed request creates no checkout. ### Accountless guest wallets `POST /api/v1/guest-wallets` creates a hosted checkout after explicit confirmation. It returns a guest key and status URL without requiring an account, email, or dashboard. Verified payment activates paid reads for that key. Anonymous requests to 26 non-MPP paid reads return `401` with a Bearer challenge and guest wallet action. The 7 direct MPP reads return `402` with a Payment challenge and the same optional action. Neither response creates checkout. See [Accountless guest wallets](/guides/guest-wallets) for creation, polling, top-ups, scope boundaries, and MCP behavior. ## First request Run this request to verify your API key: ```bash theme={null} curl -s https://xquik.com/api/v1/account \ -H "x-api-key: xq_your_api_key_here" | jq ``` **Response:** ```json theme={null} { "plan": "active", "monitorsAllowed": 9007199254740991, "monitorsUsed": 0, "monitorBilling": { "activeDailyEstimate": "0", "activeHourlyBurn": "0", "creditsPerActiveMonitorDay": "500", "creditsPerActiveMonitorHour": "21", "eventsIncluded": true, "instantCheckIntervalSeconds": 1, "unlimitedSlots": true }, "creditInfo": { "balance": "140000", "lifetimePurchased": "140000", "lifetimeUsed": "0", "autoTopupEnabled": false, "autoTopupAmountDollars": 10, "autoTopupThreshold": "50000" } } ``` Replace `xq_your_api_key_here` with your key. After `401`, confirm the `x-api-key` header and `xq_` prefix. ## Production Integration Use the [X API integration checklist](/guides/x-api-integration-checklist) before launching a client, workflow, or agent. It covers authentication, pagination, billing, rate limits, and durable writes. ## Rate limits The read bucket covers `GET`, `HEAD`, and `OPTIONS`. It allows 300 calls each second. Standard read throttles return `Retry-After: 1`. `POST`, `PUT`, and `PATCH` share a 120 per 60s user bucket. Throttled writes return `Retry-After: 60`. `DELETE` requests use a separate 60 per 60s user bucket. Throttled deletes return `Retry-After: 60`. Exceeding a bucket returns `429 rate_limit_exceeded` with `Retry-After` and a JSON `retryAfter` field. See the [Rate Limits guide](/guides/rate-limits) for backoff implementations. ## Response, Error & Event Contracts Use these references while building production integrations: * [X API integration checklist](/guides/x-api-integration-checklist) covers IDs, timestamps, pagination, normalized responses, and monitor events. * [Error handling](/guides/error-handling) lists error codes and retry actions. * [Monitor events](/api-reference/events/list) documents tweet and profile event filters. * [Webhook testing](/guides/twitter-webhook-testing) verifies signed deliveries and retries. ## Next steps Handle errors gracefully with retries and fallbacks. Understand limits and implement backoff strategies. End-to-end examples: monitors, events, and webhooks. User lookups, tweet search, trends, and media downloads. Create tweets, likes, retweets, follows, DMs, and profile updates. Check X accounts every second and receive signed webhook events. # Social Media Monitoring API for Trending Topics Source: https://docs.xquik.com/api-reference/radar/list GET /radar Monitor Reddit, GitHub, search trends, technology news, Wikipedia, prediction markets, and startup topics through one social media monitoring API with filters. ```json theme={null} { "hasMore": false, "items": [] } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Build a social media monitoring feed from ranked public topics. Radar covers Reddit, GitHub, technology news, search trends, Wikipedia, prediction markets, and startup growth records. Apply the relevant source, category, region, and time window to each request. Authenticate every Radar request. You need no active subscription. Radar consumes no credits. ## What Is a Social Media Monitoring API? A social media monitoring API turns public posts and trend signals into application-ready records. Radar exposes those records through one REST API endpoint. Each response uses structured JSON and stable field names. Radar gives every supported public source the same `RadarItem` shape. The `GET /radar` endpoint filters items by source, category, region, and age. The response ranks recent topics. Radar is not an exhaustive source archive. Radar combines several public topic sources. It does not mirror every social network. It also does not return X tweets, replies, followers, or timelines. For X-specific monitoring, [Search Tweets](/api-reference/x/search-tweets) finds brand mentions, hashtags, replies, and keyword matches. Use stable fields to compare titles, regions, timestamps, and scores. Avoid building separate source adapters. Compare trends across sources with Radar. Keep full social listening data in a dedicated platform API. Radar is not a social listening API for every major social platform. It does not replace platform APIs for unsupported social platforms. This API access returns ranked topics, not complete post, comment, or brand-mention records. Radar provides no keyword search volume or long-term history. Radar includes no more than 72 hours of history. ## Build a Trending Topics API Feed Use Radar as a trending topics API for dashboards, alerts, and research queues. Start with a narrow time window. Apply source and category together or separately. 1. Call `GET /radar` with your API key. 2. Filter one source when the request names a platform. 3. Limit the region when location affects the result. 4. Sort or filter records by `score` in your application. 5. Store each `id` to prevent duplicate dashboard rows. 6. Continue with `nextCursor` while `hasMore` stays `true`. Use `source=reddit` for Reddit posts. Use `source=github` for trending repositories. Other source identifiers follow the same pattern. This API access keeps one integration path across every supported feed. ## Is Radar Real-Time for Monitoring Trending Topics? Radar returns recently indexed topics. It does not stream every new event. Use `publishedAt` for source timing. Use `createdAt` for indexing timing. Start each scheduled poll without `after`. Use `nextCursor` only for later pages within that poll. The contract guarantees neither cross-poll cursor lifetime nor snapshots. Deduplicate records by `items[].id`. Never use an item ID to order separate polling runs. Respect every `429` rate limit response. Wait before the next poll. This pattern tracks recent changes without promising a delivery deadline. ## Can Radar Track Brand Mentions and Sentiment? Radar has no keyword query. Radar does not provide a complete brand monitoring index. Compare returned titles and descriptions against a saved brand dictionary. That comparison covers only the current Radar feed. Radar does not analyze sentiment. The `score` value measures relevance, not sentiment. Do not classify a high score as positive or negative sentiment. Use [Search Tweets](/api-reference/x/search-tweets) to query exact X mentions. Use Radar for ranked cross-source topics. This split prevents false assumptions about brand-mention coverage. ## How Do You Evaluate Radar Topic Accuracy? Evaluate each item against the source fields. Do not treat a high trend score as proof that a claim is correct or popular everywhere. 1. Match `source` and `sourceId` to the intended feed. 2. Open `url` when present. Compare its title and description with the item. 3. Compare `publishedAt` with `createdAt` to separate publication from indexing. 4. Confirm `region`, `category`, and `language` match the intended alert. 5. Interpret `metadata` with the source-specific fields documented below. 6. Deduplicate by `id`, then retain the score used for that polling run. Radar does not guarantee complete source coverage or independent fact verification. Review the original source before publishing an alert, report, or customer-facing dashboard entry. ## Integrate Radar Into a Monitoring Dashboard Keep the dashboard pipeline simple. Request one page, validate the response, and render each `RadarItem`. Save the item ID, source, source ID, score, region, and timestamps. Group rows by `source` for a multi-feed dashboard. Rank dashboard rows by the returned `score` value, then filter them by `publishedAt`. Open the returned `url` when present. Use `hasMore` and `nextCursor` for background ingestion. Never construct a cursor. Keep it with matching filters during one pagination run. Changing `source`, `category`, `region`, or `hours` starts a different result set. Choose a searchable news API when you need exact keyword or domain matching. Choose Radar when you need normalized topic discovery across its documented sources. This distinction keeps the dashboard accurate and easy to integrate. ## Headers Your API key. Xquik also accepts session cookie authentication. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Query parameters Filter with one source identifier. Use `reddit` for Reddit, `github` for GitHub, `trustmrr` for startup growth, `hacker_news` for technology news, `google_trends` for search trends, `wikipedia` for Wikipedia, or `polymarket` for prediction markets. Omit this filter to return every available stream. Filter by category. One of: `general`, `tech`, `dev`, `science`, `culture`, `politics`, `business`, `entertainment`. Omit to return all categories. Xquik assigns categories from each source and item. Look-back window in hours. Range: 1-72. Smaller values surface the most recent trends; larger values give a broader snapshot. Default is 6 hours. Items per page. Range: 1-100. Use smaller values (10-20) for quick polling, larger values for batch processing. Default is 50. Region filter. Values: `US`, `GB`, `TR`, `ES`, `DE`, `FR`, `JP`, `IN`, `BR`, `CA`, `MX`, `global`. Default `global` returns items from all regions. Cursor for pagination. Pass `nextCursor` from the previous response. Treat cursors as opaque base64 strings. Do not construct them. | Radar monitoring column | Response source | Queue rule | | ----------------------- | --------------------------------------- | ------------------------------------------------------ | | Item ID | `items[].id` | Deduplicate one indexed Radar item. | | Source record | `items[].source` and `items[].sourceId` | Preserve the original stream identity. | | Topic | `items[].title` | Display the concrete trend or discussion. | | Category | `items[].category` | Route technology, business, culture, or other topics. | | Region | `items[].region` | Keep the market with every queue row. | | Trend score | `items[].score` | Sort higher-scoring items first. | | Published at | `items[].publishedAt` | Measure source freshness. | | Source fields | `items[].metadata` | Interpret the fields documented below for that source. | | Next page | `nextCursor` | Continue only when `hasMore` is `true`. | ## Response Array of radar items sorted by score (descending). Whether more items are available after this page. Pagination cursor. Present only when `hasMore` is `true`. Pass as the `after` query parameter on the next request. ### RadarItem fields Radar item identifier. Item title. Item description. The API omits this field when the source provides no value. Link to the original source. The API omits this field when no link exists. Source image URL. Startup growth items return the logo here. The API omits this field when no image exists. Radar source identifier. Reuse this value in `source` to request the same stream. Unique identifier within the source. One of: `general`, `tech`, `dev`, `science`, `culture`, `politics`, `business`, `entertainment`. Region code (e.g. `US`, `TR`, `global`). The `language` value uses a BCP-47 code such as `en`, `tr`, or `ja`. The value `und` means the source did not identify a language. This 0-10,000 relevance score ranks trending items. Higher values indicate a stronger trend signal within the result set. Source-specific fields appear here. See the source tables below. This shows when the source published the item or Radar found it. The value follows ISO 8601. This records when Radar indexed the item. The value follows ISO 8601. ## Metadata Use `source` to interpret `metadata`. Multiword fields use camelCase in the default REST response and snake\_case through API MCP. ### Reddit Every Reddit item identifies `author`, `subreddit`, and `sourceFormat`. Current items combine public listing discovery with server-rendered post fields. They include available post content, media, and engagement metrics. Legacy rows can still identify `sourceFormat` as `json` or `rss`. | Fields | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | `author`, `authorId` | Author username and optional Reddit author ID | | `subreddit`, `subredditId`, `subredditSubscribers` | Community name and optional ID. Legacy JSON rows can include subscriber count | | `sourceFormat` | `html` for current rich items. `json` and `rss` identify legacy rows | | `score`, `upvoteRatio` | Reddit's public net score and public upvote ratio | | `estimatedUpvotes`, `estimatedDownvotes` | Estimates derived from score and ratio | | `numberComments`, `totalAwardsReceived` | Available engagement counts | | `selftext`, `contentUrl`, `domain`, `postHint`, `linkFlairText` | Available post text, destination, and labels | | `numberCrossposts`, `viewCount`, `distinguished`, `editedAt`, `galleryImageUrls`, `redditVideo` | Additional fields retained on legacy JSON rows | | `archived`, `contestMode`, `isCrosspostable`, `isMeta`, `isNsfw`, `isOriginalContent`, `isRobotIndexable`, `isSelf`, `isSpoiler`, `isVideo`, `locked`, `stickied` | Post state flags | Exact public upvote and downvote counts are unavailable. Treat `estimatedUpvotes` and `estimatedDownvotes` as estimates because Reddit may fuzz the public score and ratio. `numberComments` is a count. Comment bodies are not returned. ### Startup Growth Startup growth items always include `mrr`, `growthPercent`, `last30Days`, `total`, `customers`, `activeSubscriptions`, and `onSale`. | Fields | Description | | ----------------------------------------------------------------- | ------------------------------------------------ | | `xHandle` | Founder X username without `@` | | `category`, `country`, `foundedDate`, `targetAudience` | Available company details | | `askingPrice`, `multiple`, `paymentProvider` | Available sale and payment details | | `growthMrrPercent`, `profitMarginLast30Days`, `revenuePerVisitor` | Available reported growth and efficiency metrics | | `googleSearchImpressionsLast30Days`, `visitorsLast30Days` | Available 30-day acquisition metrics | | `rank` | Revenue rank reported by the source | Radar orders these items by reported 30-day revenue growth. `metadata.rank` is the revenue rank reported by the source. It does not equal the result position. The top-level `imageUrl` contains the startup logo when available. ### Other Sources | Source | Metadata | | ------------------------------- | ---------------------------------------------------------------- | | GitHub (`github`) | Stars added today in `starsToday` | | Technology news (`hacker_news`) | Public points and comment count in `points` and `numberComments` | | Search trends (`google_trends`) | Approximate traffic in `approxTraffic` | | Polymarket (`polymarket`) | Reported 24-hour volume in `volume24hr` | | Wikipedia (`wikipedia`) | Public page views in `views` | ## Examples ```bash cURL theme={null} curl "https://xquik.com/api/v1/radar?category=tech&hours=12&limit=10" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/radar?category=tech&hours=12&limit=10", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ); const { items, hasMore, nextCursor } = await response.json(); for (const item of items) { console.log(`[${item.source}] ${item.title} (score: ${item.score})`); } ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/radar", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"category": "tech", "hours": 12, "limit": 10}, ) data = response.json() for item in data["items"]: print(f"[{item['source']}] {item['title']} (score: {item['score']})") ``` ```go Go theme={null} req, _ := http.NewRequest("GET", "https://xquik.com/api/v1/radar?category=tech&hours=12&limit=10", nil) req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var data struct { Items []map[string]interface{} `json:"items"` HasMore bool `json:"hasMore"` NextCursor string `json:"nextCursor"` } json.NewDecoder(resp.Body).Decode(&data) for _, item := range data.Items { fmt.Printf("[%s] %s (score: %.0f)\n", item["source"], item["title"], item["score"]) } ``` **Response:** ```json theme={null} { "items": [ { "id": "12345", "title": "Open-source deployment platform launches preview environments", "description": "A self-hosted platform adds preview environments and edge functions.", "url": "https://example.com/radar/items/12345", "source": "github", "sourceId": "github_12345", "category": "dev", "region": "global", "language": "en", "score": 450, "metadata": { "starsToday": 450, "language": "TypeScript" }, "publishedAt": "2026-03-04T08:30:00.000Z", "createdAt": "2026-03-04T08:35:00.000Z" } ], "hasMore": true, "nextCursor": "NDUwfDIwMjYtMDMtMDRUMDg6MzA6MDAuMDAwWnwxMjM0NQ==" } ``` ```json theme={null} { "error": "invalid_input" } ``` Invalid `source` or `category` value. Xquik clips numeric `hours` and `limit` values to the accepted range. Non-numeric values use defaults. ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. Check the `x-api-key` header value. ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Wait for the `Retry-After` value before requesting another Radar page. ## Pagination Radar uses cursor-based pagination. When `hasMore` is `true`, pass `nextCursor` as `after` to fetch the next page. ```bash cURL theme={null} # First page curl "https://xquik.com/api/v1/radar?limit=20" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq # Next page curl "https://xquik.com/api/v1/radar?limit=20&after=NDUwfDIwMjYtMDMtMDRUMDg6MzA6MDAuMDAwWnwxMjM0NQ==" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} let cursor = undefined; const allItems = []; do { const params = new URLSearchParams({ limit: "20" }); if (cursor) params.set("after", cursor); const data = await fetch( `https://xquik.com/api/v1/radar?${params}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ).then((r) => r.json()); allItems.push(...data.items); cursor = data.hasMore ? data.nextCursor : undefined; } while (cursor); ``` ```python Python theme={null} all_items = [] cursor = None while True: params = {"limit": 20} if cursor: params["after"] = cursor data = requests.get( "https://xquik.com/api/v1/radar", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params=params, ).json() all_items.extend(data["items"]) if data["hasMore"]: cursor = data["nextCursor"] else: break ``` ```go Go theme={null} var allItems []map[string]interface{} cursor := "" for { url := "https://xquik.com/api/v1/radar?limit=20" if cursor != "" { url += "&after=" + cursor } req, _ := http.NewRequest("GET", url, nil) req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, _ := http.DefaultClient.Do(req) var data struct { Items []map[string]interface{} `json:"items"` HasMore bool `json:"hasMore"` NextCursor string `json:"nextCursor"` } json.NewDecoder(resp.Body).Decode(&data) resp.Body.Close() allItems = append(allItems, data.Items...) if !data.HasMore { break } cursor = data.NextCursor } ``` **Next steps:** [Trends guide](/guides/trends) for content strategy workflows · [Create Tweet](/api-reference/x-write/create-tweet) to post about trending topics · [Compose](/api-reference/compose/create) to generate AI-drafted tweets from radar items # Tweet Writing Style API for Cached X Profile Samples Source: https://docs.xquik.com/api-reference/styles/analyze POST /styles Cache recent tweet writing samples for one X username. Reuse fresh profiles, refresh stale samples, inspect authors, timestamps, and exact Tweet IDs safely. ```json theme={null} { "xUsername": "elonmusk", "tweetCount": 50, "isOwnAccount": true, "fetchedAt": "2025-01-15T12:00:00Z", "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!" } ] } ``` ```json theme={null} { "xUsername": "elonmusk", "tweetCount": 50, "isOwnAccount": true, "fetchedAt": "2025-01-15T12:00:00Z", "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!" } ] } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Cached response: free** · **Refresh: 1 credit per tweet returned** ## Build a Reusable Tweet Writing Profile Send one X username to cache recent tweet writing samples. Xquik searches posts from that username and stores each returned Tweet ID, text, author, and time. This route manages cache creation and refreshes. It does not generate a tone label, vocabulary summary, sentence score, or engagement analysis. Use the returned samples for your approved writing review. Use [Analyze Performance](/api-reference/styles/performance) for current likes, replies, reposts, quotes, bookmarks, and views. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/styles \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "username": "xquik" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/styles", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ username: "xquik" }), }); const result = await response.json(); if (!response.ok) { throw new Error(`${result.error}: ${result.message}`); } const cacheWasRefreshed = response.status === 201; ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/styles", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"username": "xquik"}, timeout=30, ) response.raise_for_status() result = response.json() cache_was_refreshed = response.status_code == 201 ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, err := json.Marshal(map[string]string{"username": "xquik"}) if err != nil { panic(err) } req, err := http.NewRequest( http.MethodPost, "https://xquik.com/api/v1/styles", bytes.NewReader(body), ) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { panic(fmt.Sprintf("style request failed: %v", result)) } cacheWasRefreshed := resp.StatusCode == http.StatusCreated fmt.Println(cacheWasRefreshed) } ``` ## Understand Cached and Refreshed Responses The HTTP status explains the cache action. Always inspect `fetchedAt` too. | Status | Cache condition | X request | Credit result | | ------ | ------------------------------------------------- | --------- | ----------------------------- | | `200` | Profile is under 7 days old | No | Free cached response | | `200` | Profile is older, but refresh cannot be funded | No | Existing stale cache returned | | `201` | Profile is missing or older and refresh is funded | Yes | 1 credit per returned Tweet | | `402` | No cache exists and refresh cannot be funded | No | `no_cached_style` returned | A `200` response does not guarantee a recent refresh. Compare `fetchedAt` with your freshness policy before using the samples. The route has no `force` parameter. Repeating POST while the profile is fresh returns the same cache without another X request. ## Send a Normalized X Username Send a non-empty X username without `@`, spaces, or a profile URL. Xquik lowercases the value before lookup and storage. ```json theme={null} { "username": "xquik" } ``` The request body accepts one field only: | Field | Type | Required | Rule | | ---------- | ------ | -------- | ------------------------------ | | `username` | string | Yes | Send the X handle without `@`. | Missing, empty, or non-string values return `400 invalid_input`. Preserve the returned `xUsername` value for Get, Compare, Delete, and Performance requests. ## Read the Cached Tweet Writing Samples The profile response contains exact cached examples. It does not contain derived writing conclusions. | Profile field | Meaning | Integration use | | -------------- | ---------------------------------------------- | ---------------------------------------- | | `xUsername` | Lowercase analyzed X username | Use as the profile key. | | `tweetCount` | Number of cached Tweet samples | Record the sample size and refresh cost. | | `isOwnAccount` | Ownership classification stored during refresh | Separate owned and external profiles. | | `fetchedAt` | ISO 8601 fetch time | Apply your cache freshness policy. | | `tweets` | Cached tweet writing samples | Review the source text directly. | `isOwnAccount` compares the username with the saved [X identity](/api-reference/account/x-identity) during a refresh. A cached `200` response does not recompute that flag. ## Parse Every Cached Tweet Each public `tweets[]` entry contains these fields: | Tweet field | Required | Meaning | | ---------------- | -------- | -------------------------- | | `id` | Yes | Exact Tweet ID as a string | | `text` | Yes | Cached tweet text | | `authorUsername` | No | Tweet author's X username | | `createdAt` | No | ISO 8601 publication time | Keep Tweet IDs as strings. JavaScript numbers cannot safely represent every X Tweet ID. The public Style Profile contract does not include cached media objects. Use a Tweet or media endpoint when your workflow requires images, videos, or GIFs. ```json theme={null} { "xUsername": "xquik", "tweetCount": 2, "isOwnAccount": true, "fetchedAt": "2026-08-02T12:00:00.000Z", "tweets": [ { "id": "1950882898740914261", "text": "Export tweet replies to JSON or CSV.", "authorUsername": "xquik", "createdAt": "2026-08-02T11:30:00.000Z" } ] } ``` ## Review Observable Tweet Writing Features The response provides source text for your analysis. Start with observable features before assigning subjective voice or tone labels. | Writing feature | Sample calculation | Review question | | --------------- | ------------------------------------ | ------------------------------------------ | | Post length | Median `text.length` | Does the profile prefer short tweets? | | Questions | Samples containing `?` | How often does the profile invite replies? | | Links | Samples containing an HTTP URL | How often does it cite another page? | | Hashtags | Count `#` tokens | Does it label topics explicitly? | | Openings | Compare first sentences | Which hooks recur? | | Vocabulary | Count repeated meaningful words | Which specific terms define the sample? | | Cadence | Compare available `createdAt` values | How closely were samples published? | Do not infer future engagement from writing samples. Refresh public metrics through Analyze Performance when outcome comparisons are required. ## Build a Safe Twitter Brand Voice Workflow 1. Set the [X identity](/api-reference/account/x-identity) before refreshing. 2. Send one public X username to this endpoint. 3. Branch on HTTP `200` or `201`. 4. Store `xUsername`, `fetchedAt`, and `tweetCount`. 5. Preserve every Tweet ID and text value. 6. Review observable patterns with a human editor. 7. Keep current product claims out of copied historical tweets. Use cached examples as references. Never copy another person's distinctive phrasing without authorization. ## Analyze Another Public X Account You can cache recent public tweets from another username. The resulting profile belongs only to the authenticated Xquik account. This route does not expose drafts, private posts, private analytics, followers, following, profile visits, or direct messages. ## Budget a Style Refresh Cached `200` responses are free. A `201` refresh costs 1 credit per Tweet returned and stored. Use `tweetCount` to reconcile the refresh. The endpoint does not accept a limit parameter, date range, pagination cursor, or requested sample count. When a stale cache exists without enough available credits, Xquik returns that cache with `200`. It does not delete the existing writing samples. ## Handle Style Cache Errors | Status | Error | Meaning | Recovery | | ------ | --------------------- | -------------------------------------------- | ---------------------------------------- | | `200` | None | Cached profile returned | Inspect `fetchedAt` before use. | | `201` | None | Profile fetched and stored | Record `tweetCount` for billing. | | `400` | `invalid_input` | Username is missing, empty, or not a string | Send one username string. | | `401` | `unauthenticated` | Credential is missing or invalid | Replace the API key or bearer token. | | `402` | `no_cached_style` | No cache exists and refresh cannot be funded | Top up or save approved examples. | | `429` | `rate_limit_exceeded` | Request exceeded the rate limit | Wait for `Retry-After`, then retry once. | The response widget includes every status in the canonical OpenAPI contract. ## Headers Send `x-api-key` with an Xquik API key. OAuth clients can send a bearer token through the `Authorization` header instead. Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body Non-empty X username without `@`. Xquik lowercases the value. ## Response ### 200 OK Returns a cached profile. It can be fresh or an unfunded stale fallback. ### 201 Created Returns a newly fetched and stored profile. Lowercase analyzed X username. Number of cached Tweet samples. Stored ownership classification. ISO 8601 cache fetch timestamp. Cached tweet writing samples. Exact Tweet ID. Cached tweet text. Optional author username. Optional ISO 8601 publication time. ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` Username is missing, empty, or not a string. Send one username string. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Authentication failed. Replace the missing or invalid credential. ### 402 No Cached Style ```json theme={null} { "error": "no_cached_style", "message": "No cached style for @username. Share 5-10 example tweet texts, then save via PUT /api/v1/styles/username." } ``` No cache exists and the refresh cannot be funded. Top up through the [billing page](https://dashboard.xquik.com/en/account?tab=subscription), or save approved tweet examples with [Save Custom Style](/api-reference/styles/save). ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for `Retry-After`, then retry once. ### How Do I Analyze a Twitter Writing Style? Cache one public username, then review the returned tweet text. This endpoint supplies writing samples. Your application performs any voice or tone analysis. ### Does Analyze Style Return Tone or Vocabulary Scores? No. It returns Tweet IDs, text, authors, timestamps, and cache metadata. It does not return generated style labels or scores. ### Why Did Analyze Style Return 200 Instead of 201? The route reused an existing cache. Check `fetchedAt`. A `200` can also be a stale fallback when refresh funding is unavailable. ### Can I Force a Twitter Style Refresh? No force parameter exists. Fresh profiles return from cache. Delete and recreate a profile only when that destructive workflow is appropriate. ### Can I Analyze Another Twitter Account? Yes, for recent public tweets. The cached profile remains isolated to your authenticated Xquik account. **Next:** [Get Style](/api-reference/styles/get) reads samples without a refresh. [Compare Styles](/api-reference/styles/compare) retrieves 2 profiles. # Compare 2 Cached Tweet Writing Profiles with Xquik Source: https://docs.xquik.com/api-reference/styles/compare GET /styles/compare Compare 2 cached tweet writing profiles for X usernames or custom labels. Retrieve both sample sets, authors, timestamps, counts, and ownership status. ```json theme={null} { "style1": { "xUsername": "elonmusk", "tweetCount": 50, "isOwnAccount": true, "fetchedAt": "2025-01-15T12:00:00Z", "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!" } ] }, "style2": { "xUsername": "BillGates", "tweetCount": 40, "isOwnAccount": false, "fetchedAt": "2025-01-15T12:00:00Z", "tweets": [ { "id": "9876543210", "text": "Climate change is a global challenge." } ] } } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ## Retrieve 2 Cached Tweet Writing Profiles Call this endpoint to retrieve 2 cached writing profiles together. Supply 2 X usernames, 2 custom labels, or one of each. Xquik performs 2 account-scoped cache lookups. It does not contact X, refresh tweets, or calculate a writing-style score. The response preserves request order. `style1` matches `username1`. `style2` matches `username2`. ```bash cURL theme={null} curl -G https://xquik.com/api/v1/styles/compare \ --data-urlencode "username1=xquik" \ --data-urlencode "username2=product updates" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ username1: "xquik", username2: "product updates", }); const response = await fetch( `https://xquik.com/api/v1/styles/compare?${params}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }, ); const result = await response.json(); if (!response.ok) { throw new Error(`${result.error}: ${result.message}`); } ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/styles/compare", params={"username1": "xquik", "username2": "product updates"}, headers={"x-api-key": "xq_YOUR_KEY_HERE"}, timeout=30, ) response.raise_for_status() result = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" "net/url" ) func main() { endpoint, err := url.Parse("https://xquik.com/api/v1/styles/compare") if err != nil { panic(err) } query := endpoint.Query() query.Set("username1", "xquik") query.Set("username2", "product updates") endpoint.RawQuery = query.Encode() req, err := http.NewRequest(http.MethodGet, endpoint.String(), nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } if resp.StatusCode != http.StatusOK { panic(fmt.Sprintf("style comparison failed: %v", result)) } } ``` ## Select Comparable Tweet Style Keys Both query values use the same keys as [Get Style](/api-reference/styles/get). Xquik lowercases each value before lookup. | Profile source | Query value | Example | | ------------------------------------------------------ | ------------------------ | ----------------- | | [Analyze & Cache Style](/api-reference/styles/analyze) | Returned `xUsername` | `xquik` | | [Save Custom Style](/api-reference/styles/save) | Returned lowercase label | `product updates` | | [List Styles](/api-reference/styles/list) | Any `styles[].xUsername` | `founder voice` | The query parameter names say `username`. They also accept saved custom labels. Do not send numeric database IDs. Use 2 different keys for a meaningful comparison. The API accepts the same key twice, but both response objects will contain the same profile. ## Understand What Compare Styles Returns This endpoint groups 2 stored sample sets into one response. It does not generate conclusions about voice, tone, vocabulary, readability, or sentiment. | Response field | Meaning | Comparison use | | -------------- | ------------------------------------ | -------------------------------------- | | `style1` | Profile selected by `username1` | Treat as the first sample set. | | `style2` | Profile selected by `username2` | Treat as the second sample set. | | `xUsername` | Lowercase X username or custom label | Preserve each profile key. | | `tweetCount` | Stored tweet sample count | Check whether sample sizes differ. | | `isOwnAccount` | Stored ownership classification | Separate owned and external profiles. | | `fetchedAt` | ISO 8601 cache timestamp | Check whether collection times differ. | | `tweets` | Cached or supplied tweet examples | Perform your approved comparison. | The 2 profiles can have different sample counts. They can also have different cache timestamps. Compare equivalent subsets when those differences matter. ## Inspect Every Tweet Writing Sample Both `style1.tweets[]` and `style2.tweets[]` use this public contract: | Tweet field | Required | Meaning | | ---------------- | -------- | --------------------------------------------- | | `id` | Yes | Stored Tweet ID or generated custom-sample ID | | `text` | Yes | Cached tweet or supplied writing example | | `authorUsername` | No | X author username or custom label | | `createdAt` | No | ISO 8601 tweet or custom-sample timestamp | Keep Tweet IDs as strings. JavaScript numbers cannot safely represent every X Tweet ID. Do not send generated custom-sample IDs to X tweet endpoints. They identify local writing examples only. ```json theme={null} { "style1": { "xUsername": "xquik", "tweetCount": 2, "fetchedAt": "2026-07-31T10:30:00.000Z", "isOwnAccount": true, "tweets": [ { "id": "1950882898740914261", "text": "Export tweet replies to JSON or CSV.", "authorUsername": "xquik", "createdAt": "2026-07-31T09:00:00.000Z" } ] }, "style2": { "xUsername": "product updates", "tweetCount": 2, "fetchedAt": "2026-07-31T10:35:00.000Z", "isOwnAccount": false, "tweets": [ { "id": "0", "text": "Ship notes should name the endpoint and user outcome.", "authorUsername": "product updates", "createdAt": "2026-07-31T10:35:00.000Z" } ] } } ``` ## Compare Tweet Writing Without Inventing Signals Voice describes a consistent brand personality. Tone can change with context. This API returns the source text needed for your own review. Use observable text features before assigning subjective labels: | Writing feature | Calculation | Review question | | --------------- | ------------------------------------ | ------------------------------------------- | | Post length | Compare median `text.length` | Which profile writes shorter tweets? | | Questions | Count samples containing `?` | Which profile asks readers more often? | | Links | Count samples containing an HTTP URL | Which profile cites external pages? | | Hashtags | Count `#` tokens | Which profile uses topic labels? | | Openings | Compare each sample's first sentence | Which recurring openings appear? | | Vocabulary | Compare repeated meaningful words | Which terms distinguish each profile? | | Cadence | Compare available `createdAt` values | Were the samples posted in similar periods? | Review examples in context. A small or stale cache cannot prove a permanent brand voice. Avoid copying personal phrases without authorization. ## Separate Writing Style from Twitter Analytics Compare Styles returns no likes, replies, reposts, quotes, bookmarks, views, followers, or impressions. It cannot prove which writing style performs better. Use [Analyze Performance](/api-reference/styles/performance) for current tweet engagement. Compare equivalent periods and sample sizes before drawing an outcome conclusion. ## Build a Reliable Tweet Style Comparison 1. List cached profiles with the same Xquik credential. 2. Select 2 returned profile keys. 3. Review `fetchedAt` and `tweetCount` for both profiles. 4. Refresh stale X profiles through Analyze when required. 5. Call Compare Styles with both URL-encoded keys. 6. Compare equivalent tweet subsets using observable features. 7. Preserve both source keys with any derived conclusion. The endpoint does not modify either cache. Repeating the same request returns the current stored profiles until another route changes them. ## Handle Tweet Style Comparison Errors | Status | Error | Meaning | Recovery | | ------ | --------------------- | ------------------------------------------ | --------------------------------------------- | | `200` | None | Both profiles exist | Compare `style1` and `style2` in order. | | `400` | `missing_params` | One or both query values are absent | Send both non-empty query parameters. | | `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. | | `404` | `style_not_found` | At least one account-scoped key is missing | List profiles, then create the missing cache. | | `429` | `rate_limit_exceeded` | The request exceeded the rate limit | Wait for `Retry-After`, then retry once. | A `404` response does not identify which key failed. Call Get Style for each key when you need to isolate the missing profile. The canonical contract documents no `402` response. Existing cache comparison does not require credits. ## Query Parameters First X username or saved custom style label. Xquik lowercases this value and returns its profile as `style1`. Second X username or saved custom style label. Xquik lowercases this value and returns its profile as `style2`. ## Headers Send `x-api-key` with an Xquik API key. OAuth clients can send a bearer token through the `Authorization` header instead. Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK First cached profile. Lowercase X username or custom label. Stored tweet writing sample count. Stored ownership classification. ISO 8601 cache fetch or custom-save timestamp. Cached or supplied tweet writing samples. Tweet ID or generated custom-sample ID. Cached tweet or supplied writing example. Optional author username or custom label. Optional ISO 8601 sample timestamp. Second cached profile. It follows the same contract as `style1`. ### 400 Missing Parameters ```json theme={null} { "error": "missing_params", "message": "Both usernames are required" } ``` One or both query values are missing. Send `username1` and `username2`. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Authentication failed. Replace the missing or invalid credential. ### 404 Style Not Found ```json theme={null} { "error": "style_not_found", "message": "No cached style found for this username" } ``` At least one cache key is missing for this Xquik account. List styles first. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for `Retry-After`, then retry once. ### Does Compare Styles Calculate Tone or Brand Voice? No. It returns 2 cached tweet sample sets. Your application performs any tone, voice, vocabulary, or sentence-pattern analysis. ### Can I Compare an X Username with a Custom Style? Yes. Send the analyzed username and the saved custom label as the 2 query values. Both profiles must belong to the authenticated Xquik account. ### Can I Compare Twitter Analytics for Another Account? Not with this endpoint. Compare Styles returns writing samples, not engagement metrics. Analyze Performance retrieves current metrics for cached tweets. ### Why Does Compare Styles Return 404? At least one lowercase key is missing from this account. List styles, verify both keys, then analyze or save the missing profile. **Related:** [Get Style](/api-reference/styles/get) isolates one profile. [Delete Style](/api-reference/styles/delete) removes a cached profile. # Delete Cached Twitter Writing Style Profile Source: https://docs.xquik.com/api-reference/styles/delete DELETE /styles/{id} Delete a cached Twitter writing-style profile and its analyzed tweet samples by username. Preserve the X account and every live tweet. Includes error recovery. ```text theme={null} No response body. ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/styles/elonmusk \ -H "x-api-key: xq_YOUR_KEY_HERE" -w "\n%{http_code}" ``` ```javascript Node.js theme={null} const username = "elonmusk"; const response = await fetch(`https://xquik.com/api/v1/styles/${username}`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); console.log(response.ok); // true on success (204 No Content) ``` ```python Python theme={null} import requests username = "elonmusk" response = requests.delete( f"https://xquik.com/api/v1/styles/{username}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) response.raise_for_status() # 204 No Content on success ``` ```go Go theme={null} package main import ( "fmt" "net/http" ) func main() { username := "elonmusk" req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/styles/"+username, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println(resp.StatusCode) // 204 on success } ``` ## Delete a Cached Twitter Writing Style Delete a style when its sampled tweets no longer represent the intended voice. The request removes one cached writing-style profile owned by your Xquik account. It also removes the analyzed tweet samples stored with that profile. Use the `xUsername` returned by [List Styles](/api-reference/styles/list). Place that username in the `{id}` path segment. The examples use `elonmusk`. Do not include the leading `@` character. | Deleted record | Unchanged record | | ---------------------------------- | ------------------------------------------------- | | Cached X username | The connected X account | | Stored tweet samples | Every live tweet on X | | Derived writing-style profile | X profile name, bio, avatar, and banner | | Cached tone and vocabulary signals | Followers, following, replies, likes, and reposts | Deletion is permanent. Xquik does not provide a restore endpoint for deleted style profiles. Analyze the username again to build a new cache. ## Handle a 204 Response Without JSON A successful deletion returns `204 No Content`. The response body is empty. Check `response.status === 204` or `response.ok`. Do not call `response.json()` after a successful request. ```javascript Node.js theme={null} const username = "elonmusk"; const response = await fetch( `https://xquik.com/api/v1/styles/${username}`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }, ); if (response.status !== 204) { const error = await response.json(); throw new Error(`${error.error}: ${error.message}`); } ``` Call [List Styles](/api-reference/styles/list) after deletion when reconciliation matters. The removed username should no longer appear in that account's list. ## Recover From Style Deletion Errors | Status | Meaning | Recovery | | ------ | ------------------------------------------------ | ------------------------------------------ | | `204` | The cached style was deleted | Stop. Do not parse a body. | | `401` | Authentication failed | Replace the missing or invalid credential. | | `404` | No matching cached style belongs to this account | Check the username with List Styles. | | `429` | The request exceeded the rate limit | Wait for `Retry-After`, then retry once. | A repeated deletion returns `404` after the cache is gone. Treat that result as already absent only when your workflow permits it. Never retry `404` in a loop. ### Does This Delete Tweets From X? No. This route deletes Xquik's cached analysis only. The source tweets remain on X. The connected X account also remains available for approved workflows. ### Can I Restore a Deleted Twitter Writing Style? No restore route exists. Use [Analyze & Cache Style](/api-reference/styles/analyze) to fetch current tweets and create a fresh writing-style profile. ## Path parameters Style profile ID or X username whose cached style to delete. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response Empty response body. The style was successfully deleted. ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ```json theme={null} { "error": "style_not_found", "message": "No cached style found for this username" } ``` No cached style exists for this username, or it belongs to a different account. ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [List Styles](/api-reference/styles/list) to verify the style was removed, or [Analyze & Cache Style](/api-reference/styles/analyze) to cache a new style. # Get Cached Tweet Style Samples with the Xquik API Source: https://docs.xquik.com/api-reference/styles/get GET /styles/{id} Retrieve cached tweet writing samples for one X username or custom style label. Read tweet text, authors, timestamps, sample count, and ownership status. ```json theme={null} { "xUsername": "elonmusk", "tweetCount": 50, "isOwnAccount": true, "fetchedAt": "2025-01-15T12:00:00Z", "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!" } ] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ## Retrieve One Cached Tweet Writing Profile Call this endpoint to retrieve stored tweet samples for one writing profile. The request reads Xquik's existing cache. It does not contact X or refresh tweets. Use an analyzed X username or a saved custom label as `{id}`. Xquik lowercases the path value before lookup. The route does not accept a numeric database ID. The lookup stays inside the authenticated Xquik account. Another customer can cache the same username without exposing their tweet style profile to you. ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/styles/elonmusk \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const styleKey = "elonmusk"; const response = await fetch( `https://xquik.com/api/v1/styles/${encodeURIComponent(styleKey)}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }, ); const result = await response.json(); if (!response.ok) { throw new Error(`${result.error}: ${result.message}`); } ``` ```python Python theme={null} from urllib.parse import quote import requests style_key = "elonmusk" response = requests.get( f"https://xquik.com/api/v1/styles/{quote(style_key, safe='')}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, timeout=30, ) response.raise_for_status() result = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" "net/url" ) func main() { styleKey := "elonmusk" endpoint := "https://xquik.com/api/v1/styles/" + url.PathEscape(styleKey) req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } if resp.StatusCode != http.StatusOK { panic(fmt.Sprintf("style request failed: %v", result)) } } ``` ## Choose the Correct Tweet Style Key The `{id}` name comes from the shared API path contract. The GET handler uses it as a profile key, not a numeric record ID. | Profile source | Value to send in `{id}` | Example | | ------------------------------------------------------ | ------------------------------------ | ----------------- | | [Analyze & Cache Style](/api-reference/styles/analyze) | Returned `xUsername` | `xquik` | | [Save Custom Style](/api-reference/styles/save) | Returned lowercase `xUsername` label | `product updates` | | [List Styles](/api-reference/styles/list) | Any listed `styles[].xUsername` | `founder voice` | Encode spaces and punctuation in custom labels. Do not include `@` before an X username. Preserve the response's `xUsername` value for later requests. ## Read the Cached Tweet Style Response The response contains stored tweet text and profile metadata. It does not return a generated tone summary, vocabulary list, hook score, or sentiment classification. | Field | Meaning | Integration use | | -------------- | ------------------------------------------------ | ------------------------------------------------- | | `xUsername` | Lowercase X username or custom style label | Store it as the reusable profile key. | | `tweetCount` | Number of tweet samples recorded for the profile | Display the writing sample size. | | `isOwnAccount` | Ownership classification stored with the profile | Label an owned or external writing profile. | | `fetchedAt` | ISO 8601 cache timestamp | Show when Xquik refreshed or saved the profile. | | `tweets` | Stored tweet writing samples | Feed approved samples into composition or review. | `tweetCount` describes the cached writing sample. It is not the X profile's lifetime post count. `fetchedAt` has two meanings. An analyzed profile records its last X fetch. A custom profile records when Xquik saved its supplied tweet examples. `isOwnAccount` is a stored snapshot. This GET request does not recompute it after an [X identity](/api-reference/account/x-identity) change. Set the X identity before creating or refreshing an analyzed profile. ## Parse Every Tweet Writing Sample Each `tweets[]` entry follows the public style profile contract: | Tweet field | Required | Meaning | | ---------------- | -------- | --------------------------------------------- | | `id` | Yes | Stored Tweet ID or generated custom-sample ID | | `text` | Yes | Full cached tweet or custom writing example | | `authorUsername` | No | X author username or custom style label | | `createdAt` | No | ISO 8601 tweet or custom-sample timestamp | Treat `id` as a string. X Tweet IDs can exceed JavaScript's safe integer range. Do not cast them to `number`. Custom profiles generate local sample IDs. Do not use those IDs with X tweet lookup, reply, like, repost, or media endpoints. ```json theme={null} { "xUsername": "elonmusk", "tweetCount": 20, "isOwnAccount": false, "fetchedAt": "2026-02-24T10:30:00.000Z", "tweets": [ { "id": "1893456789012345678", "text": "The future is now.", "authorUsername": "elonmusk", "createdAt": "2026-02-24T14:22:00.000Z" } ] } ``` ## Separate Tweet Style from Twitter Analytics This route retrieves writing samples. It does not fetch likes, replies, reposts, quotes, bookmarks, views, followers, or impressions. Use [Analyze Performance](/api-reference/styles/performance) when you need live engagement metrics for the cached tweets. Keep writing-style review separate from Twitter analytics for another account. | Goal | Endpoint | Result | | -------------------------------- | -------------------------------------------------------- | ------------------------------------ | | Retrieve unchanged tweet samples | Get Style | Cached text, authors, and timestamps | | Create or refresh an X profile | [Analyze Style](/api-reference/styles/analyze) | Current cached writing samples | | Store approved examples | [Save Custom Style](/api-reference/styles/save) | A custom tweet voice profile | | Compare 2 cached profiles | [Compare Styles](/api-reference/styles/compare) | Both stored sample sets | | Refresh tweet engagement | [Analyze Performance](/api-reference/styles/performance) | Likes, replies, reposts, and views | ## Build a Reliable Tweet Style Workflow 1. Call [List Styles](/api-reference/styles/list) with the same credential. 2. Select one returned `xUsername` profile key. 3. URL-encode that value and call this endpoint. 4. Store `xUsername`, `fetchedAt`, and `tweetCount` with the samples. 5. Review every `tweets[].text` value before composition. 6. Refresh the profile through Analyze only when required. Use cached samples as writing references. Never assume each sample is a current product claim, approved fact, or reusable announcement. ## Handle Tweet Style Retrieval Errors | Status | Error | Meaning | Recovery | | ------ | --------------------- | -------------------------------------- | ------------------------------------------ | | `200` | None | The cached tweet style exists | Read every documented field. | | `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. | | `404` | `style_not_found` | This account has no matching cache key | List styles or analyze the username first. | | `429` | `rate_limit_exceeded` | The request exceeded the rate limit | Wait for `Retry-After`, then retry once. | The canonical contract documents no `402` response for this cache read. A missing balance does not block retrieval of an existing style profile. ## Path Parameters Lowercase X username or saved custom style label. Xquik lowercases this value before an account-scoped lookup. It is not a numeric database ID. ## Headers Send `x-api-key` with an Xquik API key. OAuth clients can send a bearer token through the `Authorization` header instead. Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Lowercase X username or custom style label. Number of stored tweet writing samples. Stored ownership classification for this profile. ISO 8601 cache fetch or custom-save timestamp. Stored tweet writing samples. Tweet ID or generated custom-sample ID. Cached tweet or supplied writing example. Optional author username or custom label. Optional ISO 8601 sample timestamp. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Authentication failed. Replace the missing or invalid credential. ### 404 Style Not Found ```json theme={null} { "error": "style_not_found", "message": "No cached style found for this username" } ``` No matching cache key exists for this Xquik account. Call List Styles first. Analyze the X username when no profile exists. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for `Retry-After`, then retry once. ### How Do I Retrieve a Cached Tweet Writing Style? List styles, copy one `xUsername`, then send it as `{id}`. This endpoint returns the saved tweet text without refreshing the X profile. ### Can I Retrieve Another Account's Twitter Writing Style? Yes, after your Xquik account analyzes that public username. You cannot read a style profile cached by another Xquik customer. ### Does Get Style Return Twitter Analytics? No. It returns tweet writing samples and cache metadata. Use Analyze Performance for current likes, replies, reposts, and views. ### Why Does Get Style Return 404? The credential cannot access a matching lowercase username or label. Call List Styles, check the exact key, then analyze or save the profile. **Related:** [List Styles](/api-reference/styles/list) inventories profile keys. [Delete Style](/api-reference/styles/delete) removes a cached profile. # List Cached Twitter Writing Style Profiles Source: https://docs.xquik.com/api-reference/styles/list GET /styles List cached Twitter writing-style profiles for one Xquik account. Read every X username, analyzed tweet count, ownership flag, and cache fetch timestamp. ```json theme={null} { "styles": [] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/styles \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/styles", { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/styles", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/styles", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## List Cached Twitter Writing Styles Call this endpoint to inventory the writing-style profiles in one Xquik account. Each item summarizes one cached X username. The list does not include tweet text, tone analysis, vocabulary, or engagement metrics. An empty response is valid: ```json theme={null} { "styles": [] } ``` An empty `styles` array means this Xquik account has no cached profiles. Use [Analyze & Cache Style](/api-reference/styles/analyze) to create one. ## Read Each Writing Style Summary | Field | Meaning | Integration use | | -------------- | ----------------------------------------------------------------- | ---------------------------------------------------- | | `xUsername` | Normalized X username for the cached profile | Build Get, Delete, Compare, or Performance requests. | | `tweetCount` | Number of tweet samples stored in this profile | Show the analysis sample size. | | `isOwnAccount` | Whether the profile represents the authenticated user's X account | Label owned and external profile samples. | | `fetchedAt` | ISO 8601 time when the tweet samples were fetched | Decide whether to refresh the cache. | Do not interpret `tweetCount` as the X profile's lifetime tweet total. It only counts the tweet samples stored for this writing-style profile. `fetchedAt` records the cache fetch time. It is not a tweet publication time, profile update time, or writing-style creation timestamp. ## Route a Twitter Writing Style Workflow Use the summary fields to choose the next endpoint: | Goal | Next request | Input from this response | | ------------------------------------------ | -------------------------------------------------------- | ------------------------------- | | Inspect sampled tweet text | [Get Style](/api-reference/styles/get) | Pass `xUsername` in `{id}`. | | Refresh stale samples | [Analyze & Cache Style](/api-reference/styles/analyze) | Send `xUsername` as `username`. | | Compare two account voices | [Compare Styles](/api-reference/styles/compare) | Send 2 cached usernames. | | Refresh likes, replies, reposts, and views | [Analyze Performance](/api-reference/styles/performance) | Pass `xUsername` in `{id}`. | | Remove a cached profile | [Delete Style](/api-reference/styles/delete) | Pass `xUsername` in `{id}`. | The response contains no pagination cursor. Do not invent `nextCursor`, `page`, or `total` fields. Process only the summaries returned in `styles`. ## Handle List Style Errors | Status | Meaning | Recovery | | ------ | ----------------------------------- | ------------------------------------------ | | `200` | The style summary list is available | Read `styles`, including an empty array. | | `401` | Authentication failed | Replace the missing or invalid credential. | | `429` | The request exceeded the rate limit | Wait for `Retry-After`, then retry once. | ### Does List Styles Return the Analyzed Tweets? No. This endpoint returns summaries only. Use [Get Style](/api-reference/styles/get) to retrieve the stored tweet samples for one username. ### Why Is the Twitter Writing Style List Empty? The authenticated Xquik account has no cached profiles. Analyze an X username first, then call this endpoint again with the same Xquik credentials. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Array of cached style summaries. **Style object fields:** Normalized X username. Number of cached tweets. ISO 8601 timestamp when tweets were fetched. Whether this is the authenticated user's own X account. ```json theme={null} { "styles": [ { "xUsername": "elonmusk", "tweetCount": 20, "fetchedAt": "2026-02-24T10:30:00.000Z", "isOwnAccount": false }, { "xUsername": "sama", "tweetCount": 18, "fetchedAt": "2026-02-25T14:00:00.000Z", "isOwnAccount": false } ] } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. **Related:** [Analyze & Cache Style](/api-reference/styles/analyze) to add a new style, [Get Style](/api-reference/styles/get) to fetch full tweet data for a specific style, or [Compare Styles](/api-reference/styles/compare) to compare two cached styles side by side. # Tweet Analytics API for Likes, Replies & Reposts Source: https://docs.xquik.com/api-reference/styles/performance GET /styles/{id}/performance Refresh tweet analytics for cached X posts. Retrieve likes, replies, reposts, quotes, bookmarks, views, post text, IDs, tweet count, and snapshot guidance. ```json theme={null} { "xUsername": "elonmusk", "tweetCount": 5, "tweets": [ { "id": "1234567890", "text": "Excited to share our latest research findings.", "likeCount": 120, "retweetCount": 15, "replyCount": 8 } ] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ## Refresh Tweet Engagement for a Cached Style Call this endpoint to refresh public engagement counts for cached tweets. Xquik looks up each stored Tweet ID and returns current cumulative metrics. Start with [Analyze & Cache Style](/api-reference/styles/analyze). Then pass its returned `xUsername` as `{id}`. The lookup stays inside the authenticated Xquik account. This route contacts X on every request. It does not return saved analytics, historical deltas, or a precomputed engagement rate. ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/styles/xquik/performance \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const username = "xquik"; const endpoint = `https://xquik.com/api/v1/styles/${encodeURIComponent(username)}/performance`; const response = await fetch(endpoint, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const result = await response.json(); if (!response.ok) { throw new Error(`${result.error}: ${result.message}`); } const snapshot = { measuredAt: new Date().toISOString(), ...result, }; ``` ```python Python theme={null} from datetime import UTC, datetime from urllib.parse import quote import requests username = "xquik" endpoint = ( "https://xquik.com/api/v1/styles/" f"{quote(username, safe='')}/performance" ) response = requests.get( endpoint, headers={"x-api-key": "xq_YOUR_KEY_HERE"}, timeout=30, ) response.raise_for_status() result = response.json() snapshot = { "measuredAt": datetime.now(UTC).isoformat(), **result, } ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" "net/url" "time" ) func main() { username := "xquik" endpoint := "https://xquik.com/api/v1/styles/" + url.PathEscape(username) + "/performance" req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } if resp.StatusCode != http.StatusOK { panic(fmt.Sprintf("tweet analytics failed: %v", result)) } result["measuredAt"] = time.Now().UTC().Format(time.RFC3339) fmt.Println(result) } ``` ## Use an Analyzed X Username The `{id}` name comes from a shared style path. This handler uses it as a lowercase cache key, not a numeric database ID. Use an analyzed X username whose samples contain live Tweet IDs. A custom style saved from supplied text contains local sample IDs. Those IDs cannot produce X tweet analytics. | Profile source | Performance result | | -------------------------- | --------------------------------------------- | | Analyzed public X username | Refreshes public metrics for cached Tweet IDs | | Saved custom style label | Local sample IDs cannot resolve as X Tweets | | Numeric database ID | Unsupported lookup key | | Another customer's cache | Inaccessible to this credential | The route processes at most 100 cached Tweet records. Use [Get Style](/api-reference/styles/get) to inspect the stored sample count first. ## Read Every Tweet Analytics Field The response returns one row per refreshed Tweet. Each count is cumulative at request time. | Field | Meaning | Analytics use | | --------------- | ------------------------------- | -------------------------------------------- | | `id` | Live Tweet ID | Join snapshots without numeric conversion. | | `text` | Current Tweet text | Preserve the measured post with its counts. | | `likeCount` | Likes | Measure explicit appreciation. | | `replyCount` | Replies | Measure conversation activity. | | `retweetCount` | Reposts without comments | Measure direct redistribution. | | `quoteCount` | Quote posts with comments | Measure annotated redistribution. | | `bookmarkCount` | Bookmarks | Measure private saves as an aggregate count. | | `viewCount` | Public view or impression count | Use as exposure, not unique people. | Reposts and quote posts are separate counts. Do not add quotes into `retweetCount` twice. `viewCount` is not a unique-audience field. Repeat exposure can increase the count. See the [X public metric definitions](https://docs.x.com/x-api/fundamentals/metrics). ## Store Reproducible Tweet Analytics Snapshots The response does not provide a measurement timestamp or historical baseline. Record the client request time beside every successful response. Keep each Tweet ID, metric row, and measurement time together. Use this snapshot key: | Snapshot column | Source | | ---------------- | -------------------- | | Profile key | `xUsername` | | Tweet key | `tweets[].id` | | Measurement time | Client UTC timestamp | | Tweet text | `tweets[].text` | | Public counts | All 6 count fields | Do not overwrite an earlier snapshot. Store each request separately. Calculate deltas only between snapshots for the same Tweet ID. ```json theme={null} { "measuredAt": "2026-08-02T12:00:00.000Z", "xUsername": "xquik", "tweetId": "1950882898740914261", "likeCount": 420, "replyCount": 18, "retweetCount": 54, "quoteCount": 11, "bookmarkCount": 87, "viewCount": 28000 } ``` ## Calculate Useful Tweet Engagement Rates Xquik returns counts. Your application can derive rates for consistent comparisons. Label them as application metrics, not fields returned by Xquik. Let `views` equal `viewCount`. Use `null` when `views` equals zero. | Derived metric | Formula | Interpretation | | ------------------- | ------------------------------------------------ | --------------------------------- | | Public interactions | `likes + replies + reposts + quotes + bookmarks` | Total counted interaction actions | | Interaction rate | `public interactions / views` | Counted actions per view | | Conversation rate | `replies / views` | Replies per view | | Amplification rate | `(reposts + quotes) / views` | Redistributing posts per view | | Save rate | `bookmarks / views` | Bookmarks per view | | Like rate | `likes / views` | Likes per view | Do not include `viewCount` in the interaction numerator. A view is the denominator for these derived rates. Use medians when comparing several tweets. One viral post can distort an average. Keep raw counts beside every derived rate. ## Compare Current and Earlier Tweet Metrics For 2 snapshots, subtract earlier counts from later counts. Reject negative deltas until you confirm both snapshots used the same Tweet and field meaning. | Delta | Formula | | ------------- | --------------------------------------------- | | New likes | `later.likeCount - earlier.likeCount` | | New replies | `later.replyCount - earlier.replyCount` | | New reposts | `later.retweetCount - earlier.retweetCount` | | New quotes | `later.quoteCount - earlier.quoteCount` | | New bookmarks | `later.bookmarkCount - earlier.bookmarkCount` | | New views | `later.viewCount - earlier.viewCount` | Counts can change after publication. Avoid comparing posts measured at different ages without labeling that difference. ## Separate Tweet Analytics from Account Analytics This route measures only the cached tweets in one style profile. It does not return follower growth, following changes, profile visits, link clicks, audience demographics, or an account-wide Twitter analytics report. You can analyze another public X account after caching its username. The result covers cached public Tweets only. It does not expose private analytics. Use [Compare Styles](/api-reference/styles/compare) for 2 writing sample sets. That route does not fetch live engagement metrics. ## Budget a Tweet Analytics Refresh Each returned Tweet costs 1 credit. Check `tweetCount` through Get Style before requesting a refresh. The route can process up to 100 cached Tweets. Store the returned `tweetCount` with billing reconciliation. It counts metric rows returned by this request, not the profile's lifetime Tweet total. ## Handle Unavailable Cached Tweets Every cached ID must still resolve as a live X Tweet. Deleted, protected, or otherwise unavailable Tweets cannot produce a metric row. Re-analyze the username when the cache contains stale Tweet IDs. Never replace an unavailable Tweet ID with another post during snapshot reconciliation. ## Handle Tweet Analytics Errors | Status | Error | Meaning | Recovery | | ------ | ---------------------- | -------------------------------------- | ---------------------------------------- | | `200` | None | Current metrics are available | Store counts with a client timestamp. | | `401` | `unauthenticated` | The credential is missing or invalid | Replace the API key or bearer token. | | `402` | `insufficient_credits` | The balance cannot fund the request | Top up, then retry intentionally. | | `404` | `style_not_found` | No valid cached profile matches `{id}` | Analyze the username first. | | `429` | `rate_limit_exceeded` | The request exceeded the rate limit | Wait for `Retry-After`, then retry once. | The response widget includes every status in the canonical OpenAPI contract. Do not invent partial-success or asynchronous job responses. ## Path Parameters Analyzed X username whose cache contains live Tweet IDs. Xquik lowercases the value before an account-scoped lookup. It is not a numeric database ID. ## Headers Send `x-api-key` with an Xquik API key. OAuth clients can send a bearer token through the `Authorization` header instead. Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Lowercase analyzed X username. Number of Tweet metric rows returned. Current Tweet text and public engagement counts. Live Tweet ID. Current Tweet text. Current bookmark count. Current like count. Current quote-post count. Current reply count. Current repost count, excluding quotes. Current public view or impression count. ```json theme={null} { "xUsername": "xquik", "tweetCount": 1, "tweets": [ { "id": "1950882898740914261", "text": "Export tweet replies to JSON or CSV.", "bookmarkCount": 87, "likeCount": 420, "quoteCount": 11, "replyCount": 18, "retweetCount": 54, "viewCount": 28000 } ] } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Authentication failed. Replace the missing or invalid credential. ### 402 Insufficient Credits ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` The balance cannot fund the request. Top up through the [billing page](https://dashboard.xquik.com/en/account?tab=subscription). ### 404 Style Not Found ```json theme={null} { "error": "style_not_found", "message": "No cached style found for this username" } ``` No analyzed profile exists for this username and Xquik account. Analyze it first. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for `Retry-After`, then retry once. ### How Do I Check Tweet Analytics Through the API? Analyze an X username, then call this route with its returned `xUsername`. Store each response with your own UTC measurement timestamp. ### Does This Return Live Twitter Analytics? Yes. Each request refreshes current public counts for cached Tweet IDs. It does not return private metrics or historical deltas. ### Can I Check Twitter Analytics for Another Account? Yes, for public Tweets cached by your Xquik account. The response excludes private account analytics, follower growth, profile visits, and link clicks. ### Is `viewCount` a Unique Viewer Count? No. Treat it as exposure, not unique people. Repeat views can increase the count. ### Why Does Tweet Analytics Return 404? The Xquik account has no analyzed profile for that lowercase username. Call Analyze Style, then retry with the returned `xUsername`. **Related:** [Get Style](/api-reference/styles/get) inspects cached Tweet IDs. [List Styles](/api-reference/styles/list) inventories analyzed profiles. # Save Tweet Writing Samples for a Reusable X Style Source: https://docs.xquik.com/api-reference/styles/save PUT /styles/{id} Save 1-100 approved tweet examples as a reusable writing profile. Preserve wording, labels, timestamps, local IDs, replacement behavior, and account scope. ```json theme={null} { "xUsername": "professional voice", "tweetCount": 1, "isOwnAccount": false, "fetchedAt": "2026-08-02T18:30:00.000Z", "tweets": [ { "id": "0", "text": "Excited to share our latest research findings.", "authorUsername": "professional voice", "createdAt": "2026-08-02T18:30:00.000Z" } ] } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Save approved Tweet text as a reusable, account-scoped writing profile. Xquik preserves each example instead of generating subjective style labels. The endpoint stores 1-100 Tweet objects under one lowercase label. It trims outer whitespace from every example. Internal punctuation, casing, links, and line breaks remain available for later review. It does not infer tone, vocabulary, sentiment, readability, or engagement. It also does not fetch likes, replies, reposts, quotes, views, or followers. ## Save Approved Tweet Writing Samples Use this endpoint when you already have reviewed examples. Those examples can come from approved drafts, published Tweets, or internal writing guidelines. | Tweet style profile column | Request or response source | Reuse rule | | -------------------------- | ---------------------------------- | ----------------------------------------- | | Profile key | Body `label` | Reuse the returned lowercase key. | | Source tweets | Request `tweets[].text` | Review every supplied post before saving. | | Sample ID | Response `tweets[].id` | Treat it as a local array position. | | Sample author | Response `tweets[].authorUsername` | Expect the normalized label. | | Sample timestamp | Response `tweets[].createdAt` | Record the style save time. | | Profile timestamp | Response `fetchedAt` | Detect later replacements. | | Ownership flag | Response `isOwnAccount` | Expect `false` for custom samples. | These local sample IDs are not X Tweet IDs. Do not send them to Tweet lookup or Tweet analytics endpoints. ## Choose Representative Tweet Examples A useful Tweet writing profile needs intentional examples. Save posts that demonstrate the patterns you want future drafts to follow. | Writing feature | Include when relevant | Review before saving | | ------------------ | ----------------------------------------- | -------------------------------- | | Opening structure | Questions, statements, or short hooks | Remove accidental clickbait | | Sentence length | Short posts and longer explanations | Keep the intended pacing | | Formatting | Paragraphs, lists, or single lines | Preserve meaningful line breaks | | Punctuation | Colons, parentheses, or exclamation marks | Remove unapproved habits | | Calls to action | Replies, links, or product prompts | Keep claims accurate | | X vocabulary | Product names and audience terms | Remove internal jargon | | Emoji and hashtags | Only approved usage | Avoid one-off campaign tags | | Replies | Useful support or community responses | Exclude private customer details | Do not treat every published Tweet as a good example. Remove crisis posts, temporary promotions, outdated claims, and accidental wording. The endpoint stores text exactly after trimming outer whitespace. It does not decide whether an example fits your Twitter writing style. ## Keep the Path and Label Aligned The body `label` controls the stored profile key. Xquik trims it and converts it to lowercase before saving. The current PUT route does not reconcile `{id}` with `label`. Send the same value in both places. This keeps request URLs, logs, and response keys clear. ```text theme={null} PUT /api/v1/styles/product-updates body.label = "product-updates" response.xUsername = "product-updates" ``` Spaces are valid inside `label`. Encode them when using the same path value. A simple hyphenated label avoids confusing URLs. ## Replace an Existing Tweet Style Profiles belong to the authenticated Xquik account. The normalized body label forms the account-scoped storage key. Sending the same label replaces the entire saved Tweet array. It also resets every local sample ID and save timestamp. Send the complete approved set during every replacement. Omitting an old example removes it from the saved profile. Changing only the path `{id}` does not rename a profile. Change `label` to create or replace another normalized key. Delete the old key separately. ## Reuse Saved Tweet Examples Use the returned `xUsername` value as the profile key. The name remains `xUsername` for both analyzed accounts and custom labels. | Next operation | Key to send | Purpose | | --------------------------------------------------- | -------------------------- | ---------------------------------------- | | [Get Style](/api-reference/styles/get) | Path `{id}` | Retrieve all saved examples | | [List Styles](/api-reference/styles/list) | None | Review available labels and counts | | [Compare Styles](/api-reference/styles/compare) | `username1` or `username2` | Load 2 sample sets side by side | | [Build a Post Draft](/api-reference/compose/create) | `styleUsername` | Return matched examples as `styleTweets` | | [Delete Style](/api-reference/styles/delete) | Path `{id}` | Remove the profile | Custom samples contain local sequential IDs. Therefore, they cannot provide live likes, replies, reposts, quotes, bookmarks, or view counts. ## Request Examples ```bash cURL theme={null} curl -X PUT https://xquik.com/api/v1/styles/product-updates \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "label": "product-updates", "tweets": [ {"text": "Shipped faster follower exports today. CSV files now preserve profile IDs."}, {"text": "New: filter Tweet replies before sending results to your webhook."}, {"text": "Building an X monitor? Start with one profile and verify every event."} ] }' | jq ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/styles/product-updates", { method: "PUT", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ label: "product-updates", tweets: [ { text: "Shipped faster follower exports today. CSV files now preserve profile IDs.", }, { text: "New: filter Tweet replies before sending results to your webhook.", }, { text: "Building an X monitor? Start with one profile and verify every event.", }, ], }), }, ); const result = await response.json(); if (!response.ok) { throw new Error(`${response.status}: ${result.message}`); } console.log(result.xUsername, result.tweetCount); ``` ```python Python theme={null} import requests response = requests.put( "https://xquik.com/api/v1/styles/product-updates", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "label": "product-updates", "tweets": [ { "text": ( "Shipped faster follower exports today. " "CSV files now preserve profile IDs." ) }, { "text": ( "New: filter Tweet replies before sending results " "to your webhook." ) }, { "text": ( "Building an X monitor? Start with one profile " "and verify every event." ) }, ], }, timeout=30, ) response.raise_for_status() result = response.json() print(result["xUsername"], result["tweetCount"]) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, err := json.Marshal(map[string]interface{}{ "label": "product-updates", "tweets": []map[string]string{ {"text": "Shipped faster follower exports today. CSV files now preserve profile IDs."}, {"text": "New: filter Tweet replies before sending results to your webhook."}, {"text": "Building an X monitor? Start with one profile and verify every event."}, }, }) if err != nil { panic(err) } request, err := http.NewRequest( http.MethodPut, "https://xquik.com/api/v1/styles/product-updates", bytes.NewReader(body), ) if err != nil { panic(err) } request.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") request.Header.Set("Content-Type", "application/json") response, err := http.DefaultClient.Do(request) if err != nil { panic(err) } defer response.Body.Close() var result map[string]interface{} if err := json.NewDecoder(response.Body).Decode(&result); err != nil { panic(err) } if response.StatusCode != http.StatusOK { panic(fmt.Sprintf("style save failed: %v", result)) } fmt.Println(result["xUsername"], result["tweetCount"]) } ``` ## Path Parameters Route identifier. Keep it equal to the body `label`. The body label controls the stored profile key for PUT requests. ## Headers Send `x-api-key` with an Xquik API key. OAuth clients can send a bearer token through the `Authorization` header instead. Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body Profile label containing 1-50 characters. It must start with a letter, number, or underscore. It accepts letters, numbers, spaces, dots, hyphens, apostrophes, and underscores. Xquik trims and lowercases this value. Complete array of 1-100 approved Tweet examples. Non-empty Tweet text. Xquik trims outer whitespace before storage. ## Response ### 200 OK Returns the complete saved profile after insertion or replacement. Normalized lowercase value from the body `label`. Number of saved examples. Matches the submitted array length. Always `false` for a custom profile created from supplied text. ISO 8601 save timestamp shared by every returned sample. Complete saved Tweet sample array. Local zero-based array position. This is not an X Tweet ID. Submitted Tweet text after trimming outer whitespace. Normalized custom profile label, not an X account lookup. ISO 8601 save timestamp, not an X publication time. ```json theme={null} { "xUsername": "product-updates", "tweetCount": 3, "isOwnAccount": false, "fetchedAt": "2026-08-02T18:30:00.000Z", "tweets": [ { "id": "0", "text": "Shipped faster follower exports today. CSV files now preserve profile IDs.", "authorUsername": "product-updates", "createdAt": "2026-08-02T18:30:00.000Z" }, { "id": "1", "text": "New: filter Tweet replies before sending results to your webhook.", "authorUsername": "product-updates", "createdAt": "2026-08-02T18:30:00.000Z" }, { "id": "2", "text": "Building an X monitor? Start with one profile and verify every event.", "authorUsername": "product-updates", "createdAt": "2026-08-02T18:30:00.000Z" } ] } ``` ## Handle Save-Style Errors | Status | Error | Cause | Fix | | ------ | --------------------- | -------------------------------------- | ---------------------------------------- | | `200` | None | Profile saved or replaced | Store `xUsername` for later requests. | | `400` | `invalid_input` | Label or Tweet array failed validation | Correct the rejected field. | | `401` | `unauthenticated` | Credential is missing or invalid | Replace the API key or bearer token. | | `429` | `rate_limit_exceeded` | Request exceeded the rate limit | Wait for `Retry-After`, then retry once. | The response widget includes every status in the canonical OpenAPI contract. ### 400 Invalid Input A request returns `400` when any required input fails validation. | Invalid input | Required correction | | ------------------------------------ | ------------------------------------------------------------------------- | | Missing or non-string `label` | Send one label string. | | Blank `label` | Add at least one visible character. | | Label longer than 50 characters | Shorten the label. | | Unsupported label character | Use letters, numbers, spaces, dots, hyphens, apostrophes, or underscores. | | Missing or non-array `tweets` | Send an array of Tweet objects. | | Empty `tweets` array | Add at least one example. | | More than 100 Tweet objects | Split or reduce the sample set. | | Missing, non-string, or blank `text` | Send non-empty text for every object. | Label validation can return a specific message. Other body failures use the general invalid-input response. ```json theme={null} { "error": "invalid_input", "message": "Label can only contain letters, numbers, spaces, dots, hyphens, and apostrophes." } ``` The validator also accepts underscores. Its current message omits that character even though the public validator accepts it. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Replace the credential before retrying. Do not retry invalid credentials in a loop. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` header. Then retry the complete replacement body. ## Tweet Style Save Checklist * Review every Tweet example before sending it. * Remove customer names, private replies, and temporary claims. * Keep the path `{id}` aligned with the body `label`. * Send 1-100 objects with non-empty `text` strings. * Store the returned lowercase `xUsername` key. * Confirm `tweetCount` matches the intended sample count. * Expect `isOwnAccount` to remain `false`. * Treat returned sample IDs as local positions. * Preserve the full array before replacing an existing profile. * Handle `400`, `401`, and `429` explicitly. ## Tweet Writing Style Questions ### What Does Save Style Analyze? Nothing. Save Style stores the supplied Tweet text and profile metadata. It does not calculate tone, vocabulary, sentiment, or performance. ### How Many Tweet Examples Can I Save? Send between 1 and 100 Tweet objects. Prefer examples that demonstrate repeatable wording, structure, replies, links, and calls to action. ### Can I Update an Existing Twitter Writing Style? Yes. Send the same normalized body label with the complete replacement array. The operation replaces every stored example under that account-scoped key. ### Does the Path ID Rename a Tweet Style? No. The body label controls the stored key. Keep both values aligned, and delete the old key after creating a new label. ### Can Saved Tweet Samples Provide Engagement Analytics? No. Custom samples use local sequential IDs. Use cached live Tweet samples for likes, replies, reposts, quotes, bookmarks, and view counts. ### Can Another Xquik Account Read My Saved Style? No. Retrieval uses both the authenticated user and normalized profile key. Another Xquik account has a separate style namespace. ### How Do I Use the Style in a Post Draft? Pass the returned `xUsername` as `styleUsername` to [Build a Post Draft](/api-reference/compose/create). The response can include the matched examples in `styleTweets`. Compare [Analyze Style](/api-reference/styles/analyze) before saving custom examples. Analyze Style caches Tweets from one X username. Save Style stores only the text you supply. # Create Xquik Support Ticket with Media API Source: https://docs.xquik.com/api-reference/support/create POST /support/tickets Open a private support ticket with a subject, message, screenshots, or videos. Save its ticket ID for replies, status updates, and downloads. See costs. ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "attachments": [] } ``` ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "attachments": [] } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "idempotency_key_conflict", "message": "Reuse this Idempotency-Key only with the original request." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Choose A New Support Ticket Use this route to open a new conversation with support. Send JSON for text-only tickets and multipart data for attachments. Use reply only after the response provides a ticket ID. **Free** - does not consume credits Support tickets are free for all authenticated users. Use JSON for text-only tickets. Use `multipart/form-data` when attaching media. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/support/tickets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: replace-with-one-random-value" \ -H "Content-Type: application/json" \ -d '{ "subject": "Cannot connect X account", "body": "I keep getting a connection error when trying to link my account." }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/support/tickets", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": crypto.randomUUID(), "Content-Type": "application/json", }, body: JSON.stringify({ subject: "Cannot connect X account", body: "I keep getting a connection error when trying to link my account.", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/support/tickets", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "replace-with-one-random-value", }, json={ "subject": "Cannot connect X account", "body": "I keep getting a connection error when trying to link my account.", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "subject": "Cannot connect X account", "body": "I keep getting a connection error when trying to link my account.", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/support/tickets", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "replace-with-one-random-value") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` | Support ticket receipt column | Request or response source | Handoff rule | | ----------------------------- | ---------------------------- | -------------------------------------------------------- | | Idempotency key | Request header | Reuse it only for an identical retry. | | Ticket subject | Request `subject` | State the affected tweet, follower, or account workflow. | | Initial message | Request `body` | Include the failure and attempted fix. | | Ticket ID | Response `publicId` | Use this ID for status and reply requests. | | Attachment ID | `attachments[].publicId` | Keep the private media receipt with the ticket. | | Attachment state | `attachments[].status` | Continue only with ready evidence. | | Safe replay | `Idempotency-Replayed: true` | Reuse the original ticket response. | ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Use `application/json` for text only. Use `multipart/form-data` for media. Generate one random value for this submission. Reuse it only when retrying identical text and attachments. A replay returns the original ticket with `Idempotency-Replayed: true`. ## Body Ticket subject. 1-500 characters. Initial message. 1-10,000 characters. Up to 4 JPEG, PNG, GIF, WebP, MP4, MOV, or WebM files. Images can be 10 MB each. Videos can be 25 MB each. Combined media can be 30 MB. For a media-only ticket, omit `body` and include at least 1 attachment. ```bash cURL With Media theme={null} curl -X POST https://xquik.com/api/v1/support/tickets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: replace-with-one-random-value" \ -F "subject=Video upload problem" \ -F "body=The attached recording shows the failure." \ -F "attachments=@screen.png" \ -F "attachments=@recording.mp4" | jq ``` ## Response ### 201 Created Unique ticket public ID. Created media receipts. Private attachment public ID. Upload status: `ready` or `failed`. ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "attachments": [ { "publicId": "att_a1b2c3d4e5f6a1b2c3d4e5f6", "status": "ready" } ] } ``` ### 200 Replayed Returns the original response after a safe retry. The `Idempotency-Replayed` response header is `true`. ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` Missing or invalid `subject` or `body` field. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Missing or invalid API key. ### 409 Conflict The `Idempotency-Key` already belongs to different text or attachments. Generate a new key for the changed request. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before opening another ticket. **Next steps:** [Support Media](/api-reference/support/media) explains privacy, formats, limits, status handling, and downloads. # Xquik Support Ticket API | Get Request Details Source: https://docs.xquik.com/api-reference/support/get GET /support/tickets/{id} Retrieve one support ticket by ID with subject, open or resolved status, complete message history, private attachments, and timestamps. See API fields. ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "subject": "Cannot connect X account", "status": "open", "createdAt": "2025-01-15T12:00:00Z", "updatedAt": "2025-01-16T09:30:00Z", "messages": [ { "body": "I am unable to connect my X account.", "sender": "user", "createdAt": "2025-01-15T12:00:00Z", "attachments": [] } ] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Choose One Ticket History Use this route when one ticket ID needs full message history. Use list for summaries across tickets. Use reply or update only when the conversation or status must change. **Free** - does not consume credits Support tickets are free for all authenticated users. ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/support/tickets/tkt_a1b2c3d4e5f6a1b2c3d4e5f6 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const ticketId = "tkt_a1b2c3d4e5f6a1b2c3d4e5f6"; const response = await fetch(`https://xquik.com/api/v1/support/tickets/${ticketId}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); ``` ```python Python theme={null} import requests ticket_id = "tkt_a1b2c3d4e5f6a1b2c3d4e5f6" response = requests.get( f"https://xquik.com/api/v1/support/tickets/{ticket_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { ticketID := "tkt_a1b2c3d4e5f6a1b2c3d4e5f6" req, err := http.NewRequest("GET", "https://xquik.com/api/v1/support/tickets/"+ticketID, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` | Support ticket history column | Response source | Review rule | | ----------------------------- | --------------------------------- | ----------------------------------------------------- | | Ticket ID | `publicId` | Match the requested ticket before processing. | | Ticket subject | `subject` | Keep the reported workflow visible. | | Current state | `status` | Route open, in-progress, resolved, or closed tickets. | | Message author | `messages[].sender` | Separate user and support replies. | | Message text | `messages[].body` | Preserve the chronological investigation record. | | Attachment state | `messages[].attachments[].status` | Download only ready attachments. | | Updated at | `updatedAt` | Stop polling after the expected state arrives. | ## Path parameters The ticket public ID (e.g. `tkt_a1b2c3d4e5f6a1b2c3d4e5f6`). Returned when you [create a ticket](/api-reference/support/create) or [list tickets](/api-reference/support/list). ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Unique ticket public ID. Ticket subject. Current status: `open`, `in_progress`, `resolved`, or `closed`. ISO 8601 creation timestamp. ISO 8601 last update timestamp. Array of message objects, ordered chronologically. Message content. Who sent the message: `user` or `support`. ISO 8601 timestamp of when the message was sent. Private image and video attachments. Attachment public ID. Original filename. Validated media type. `image` or `video`. File size in bytes. `ready`, `pending`, or `failed`. Authenticated download path. ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "subject": "Cannot connect X account", "status": "open", "createdAt": "2026-03-18T10:00:00Z", "updatedAt": "2026-03-18T12:30:00Z", "messages": [ { "body": "I keep getting a connection error when trying to link my account.", "sender": "user", "createdAt": "2026-03-18T10:00:00Z", "attachments": [ { "publicId": "att_a1b2c3d4e5f6a1b2c3d4e5f6", "filename": "recording.mp4", "contentType": "video/mp4", "kind": "video", "sizeBytes": 18294386, "status": "ready", "url": "/api/v1/support/attachments/att_a1b2c3d4e5f6a1b2c3d4e5f6" } ] }, { "body": "Could you try disconnecting and reconnecting your account from the dashboard?", "sender": "support", "createdAt": "2026-03-18T11:15:00Z" } ] } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` No ticket exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Wait for the `Retry-After` value before fetching the ticket again. **Related:** [Support Media](/api-reference/support/media) explains how agents should handle attachment statuses and downloads. # Support Ticket API: List Xquik Tickets & Status Source: https://docs.xquik.com/api-reference/support/list GET /support/tickets List up to 200 Xquik support tickets in recent-update order. Review each ticket ID, subject, status, message count, creation time & latest update timestamp. ```json theme={null} { "tickets": [ { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "subject": "Cannot connect X account", "status": "open", "messageCount": 2, "createdAt": "2025-01-15T12:00:00Z" } ] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## List Support Tickets for Xquik Workflows List the signed-in account's Xquik support tickets. Review connection failures, tweet write errors, follower exports, monitor alerts, webhooks, or billing cases. This endpoint returns the documented ticket summary fields. Fetch [Get Ticket](/api-reference/support/get) when you need message bodies or attachment metadata. **Free** - does not consume credits ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/support/tickets \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/support/tickets", { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const result = await response.json(); if (!response.ok) throw new Error(JSON.stringify(result)); const activeTickets = result.tickets.filter((ticket) => ["open", "in_progress"].includes(ticket.status), ); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/support/tickets", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) result = response.json() response.raise_for_status() active_tickets = [ ticket for ticket in result["tickets"] if ticket["status"] in {"open", "in_progress"} ] ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/support/tickets", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Read the Support Ticket Inventory Use the list as a compact support queue. Store ticket IDs and timestamps before requesting any detailed history. | Support queue column | Response field | Review rule | | -------------------- | -------------- | ------------------------------------------------------------ | | Ticket ID | `publicId` | Use this stable ID for details, replies, and status changes. | | Reported issue | `subject` | Keep the affected Xquik workflow visible. | | Current state | `status` | Route open, in-progress, resolved, and closed cases. | | Conversation size | `messageCount` | Compare with the previously stored count. | | Opened time | `createdAt` | Measure the ticket's age. | | Latest activity | `updatedAt` | Process recently changed tickets first. | The endpoint returns up to 200 tickets. It sorts the newest `updatedAt` value first. The request has no pagination, search, or status query parameter. Ask [support](mailto:support@xquik.com) when you need an older ticket. Filter the returned array locally. Match `publicId`, `subject`, or `status` without sending another list request. ## Interpret Support Ticket Status The support ticket API returns 4 status values. Listing tickets never changes any status. | Status | Meaning | Next action | | ------------- | ----------------------------------------------------- | ---------------------------------------- | | `open` | A new ticket or user reply needs review. | Read the complete message history. | | `in_progress` | Support has responded or continues the investigation. | Check the newest support message. | | `resolved` | The reported workflow has a proposed resolution. | Verify the fix before reopening. | | `closed` | The conversation is complete. | Keep the ticket ID for future reference. | Users can set `open`, `resolved`, or `closed` through the update route. Support sets `in_progress`. Do not invent another state in client code. ## Build a Support Ticket Review Queue 1. Call `GET /support/tickets` with one Xquik API key. 2. Save the returned `publicId` and `updatedAt` values. 3. Keep the response order for a recent-activity queue. 4. Compare each `messageCount` with your previous inventory. 5. Fetch ticket details when the count or timestamp changes. 6. Read each message and attachment status before replying. 7. Update the ticket only after confirming the intended state. Use [Reply to Ticket](/api-reference/support/reply) for new investigation details. Use [Update Ticket Status](/api-reference/support/update) for workflow state changes. These routes preserve the existing ticket ID. Do not create a duplicate ticket for every retry. Reuse the original `publicId` while the subject describes the same problem. ## Triage X API and Twitter Scraper Problems Keep ticket subjects specific. Name the affected tweet, follower, monitor, webhook, X account, or billing workflow. Useful local queue categories include: * X account connection or reauthentication. * Tweet creation, deletion, reply, like, or repost actions. * Follower, following, timeline, reply, or media exports. * Keyword monitors, account monitors, and webhook deliveries. * API key authentication, credits, subscription checkout, or top-ups. The list response does not explain the failure. Fetch the matching ticket before making a product decision. For a `429` response, wait for `Retry-After`. Reuse the last successful inventory during that pause. ## Answer Support Ticket API Questions ### Does the List Include Ticket Messages? The current response returns `messageCount`, not message text. Fetch the ticket by `publicId` to read its chronological conversation. ### Does the List Include Attachments? The current response omits attachment filenames, types, sizes, states, and download URLs. Fetch the detailed ticket to inspect attachments. ### Can I Search Support Tickets by Subject? The endpoint accepts no search query. Filter the returned ticket summaries by `subject` in your client. ### Can I Filter Tickets by Status? The endpoint accepts no status query. Filter `open`, `in_progress`, `resolved`, or `closed` after receiving the list. ### Which Ticket Should I Read First? Start with the first changed ticket. Results already use descending `updatedAt` order. ### Does Listing Tickets Consume Xquik Credits? No. Listing support tickets is free for authenticated accounts. ## Headers Your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). An OAuth bearer token formatted as `Bearer YOUR_TOKEN`. Send this header or `x-api-key`, not both. ## Response ### 200 OK Array of ticket objects. Unique ticket public ID. Ticket subject. Current status: `open`, `in_progress`, `resolved`, or `closed`. Total number of messages in the ticket. ISO 8601 creation timestamp. ISO 8601 last update timestamp. ```json theme={null} { "tickets": [ { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "subject": "Cannot connect X account", "status": "open", "messageCount": 3, "createdAt": "2026-03-18T10:00:00Z", "updatedAt": "2026-03-18T12:30:00Z" } ] } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Wait for the `Retry-After` value before listing tickets again. **Related:** [Create Ticket](/api-reference/support/create) to open a new ticket, or [Get Ticket](/api-reference/support/get) to fetch details and message history for a specific ticket. # Xquik Support Ticket API | Upload Request Media Source: https://docs.xquik.com/api-reference/support/media GET /support/attachments/{id} Upload or download private support ticket screenshots and videos with authenticated multipart requests and attachment IDs. Includes exact API examples. ```text theme={null} ``` ```text theme={null} ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "invalid_range", "message": "Use one valid byte range." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
Support attachments stay private. Every download requires authentication and access to the parent ticket. ```bash cURL theme={null} curl https://xquik.com/api/v1/support/attachments/att_a1b2c3d4e5f6a1b2c3d4e5f6 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ --output attachment.bin ``` ```javascript Node.js theme={null} const attachmentId = "att_a1b2c3d4e5f6a1b2c3d4e5f6"; const response = await fetch( `https://xquik.com/api/v1/support/attachments/${attachmentId}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ); const bytes = new Uint8Array(await response.arrayBuffer()); ``` ```python Python theme={null} import requests attachment_id = "att_a1b2c3d4e5f6a1b2c3d4e5f6" response = requests.get( f"https://xquik.com/api/v1/support/attachments/{attachment_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) response.raise_for_status() media_bytes = response.content ``` ## Path parameters Attachment public ID from a ticket message or upload receipt. ## Headers Your API key. Session cookie authentication is also supported. One standard byte range for video seeking or resumable downloads. Example: `bytes=0-1048575`. ## Supported media | Kind | Formats | Per-file limit | | ----- | -------------------- | -------------: | | Image | JPEG, PNG, GIF, WebP | 10 MB | | Video | MP4, MOV, WebM | 25 MB | Each message accepts up to 4 files and 30 MB combined. Additional per-account upload limits protect service availability. A `429` response includes `Retry-After`. Xquik validates file signatures instead of trusting the declared content type. Rename-only format changes fail validation. ## Agent workflow 1. Send multipart fields named `attachments`. 2. Read each returned receipt. 3. Treat `ready` as downloadable. 4. Treat `failed` as unavailable and ask the user to retry. 5. Fetch the ticket before downloading media. 6. Use the returned authenticated `url` unchanged. 7. Reuse the submission `Idempotency-Key` when a network retry is required. Ticket and reply retries return the original media receipts. This prevents duplicate tickets, messages, and stored files after a lost response. Never convert the relative `url` into a public link. It requires the same account authentication as the ticket. ## Range example Video downloads support one standard byte range. Use ranges for seeking or resumable playback. ```bash theme={null} curl https://xquik.com/api/v1/support/attachments/att_a1b2c3d4e5f6a1b2c3d4e5f6 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Range: bytes=0-1048575" \ --output video-part.bin ``` ## Response Returns the complete image or video body with its validated content type. Returns the requested byte range with `Content-Range` and `Accept-Ranges: bytes`. Refresh authentication before retrying. The media is missing, unavailable, or outside the authenticated account. Correct the `Range` header before retrying. Wait for the `Retry-After` value. ## Agent status map | Status | Meaning | Agent action | | ------ | ----------------------- | -------------------------------------- | | `200` | Complete media | Read or save the body. | | `206` | Requested byte range | Continue range requests when needed. | | `401` | Authentication failed | Refresh authentication. | | `404` | Missing or unauthorized | Do not reveal which condition applied. | | `416` | Invalid range | Correct the `Range` header. | | `429` | Download limit reached | Wait for `Retry-After`. | Create media with [Create Ticket](/api-reference/support/create) or [Reply to Ticket](/api-reference/support/reply). Read attachment metadata with [Get Ticket](/api-reference/support/get). # Xquik Support Ticket API | Reply to Request Source: https://docs.xquik.com/api-reference/support/reply POST /support/tickets/{id}/messages Reply to a support ticket with text, screenshots, or videos. Attach private media IDs and inspect the saved conversation message. See response fields. ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "attachments": [] } ``` ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "attachments": [] } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "idempotency_key_conflict", "message": "Reuse this Idempotency-Key only with the original request." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Support tickets are free for all authenticated users. Use JSON for text-only replies. Use `multipart/form-data` when attaching media. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/support/tickets/tkt_a1b2c3d4e5f6a1b2c3d4e5f6/messages \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: replace-with-one-random-value" \ -H "Content-Type: application/json" \ -d '{ "body": "That worked, thank you!" }' | jq ``` ```javascript Node.js theme={null} const ticketId = "tkt_a1b2c3d4e5f6a1b2c3d4e5f6"; const response = await fetch(`https://xquik.com/api/v1/support/tickets/${ticketId}/messages`, { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": crypto.randomUUID(), "Content-Type": "application/json", }, body: JSON.stringify({ body: "That worked, thank you!", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests ticket_id = "tkt_a1b2c3d4e5f6a1b2c3d4e5f6" response = requests.post( f"https://xquik.com/api/v1/support/tickets/{ticket_id}/messages", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "replace-with-one-random-value", }, json={"body": "That worked, thank you!"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { ticketID := "tkt_a1b2c3d4e5f6a1b2c3d4e5f6" body, _ := json.Marshal(map[string]interface{}{ "body": "That worked, thank you!", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/support/tickets/"+ticketID+"/messages", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "replace-with-one-random-value") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Send a Durable Support Reply Read the ticket before composing a response. Confirm its public ID, status, latest sender, and most recent message. This prevents a reply from repeating an answer already added by another operator. Choose the request format from the actual content. Send JSON for text only. Use multipart form fields when screenshots or videos are required. Write a specific message body. Reference the observed problem, completed check, or requested next step. Avoid pasting secrets, API keys, or private browser session details. Review every attachment before upload. Use only the documented image and video formats. Keep each file and the combined request within the documented size limits. Generate one idempotency key after the text and attachments are final. Reuse it only when the identical request loses its response. Changed text or files require a new key. Interpret reply outcomes precisely: * `201` means the new message was created. * `200` with `Idempotency-Replayed` returns the earlier message. * `409` means the key already represents different content. * `404` means the ticket is missing or belongs elsewhere. * `429` requires waiting for `Retry-After`. Store the ticket ID, idempotency key, reply time, and attachment receipts. Do not store attachment bytes in shared logs. Fetch the ticket again after creation. Confirm the message appears once and the sender is correct. Inspect each attachment status before telling the user that a screenshot or video is available. ## Path parameters The ticket public ID (e.g. `tkt_a1b2c3d4e5f6a1b2c3d4e5f6`). Returned when you [create a ticket](/api-reference/support/create) or [list tickets](/api-reference/support/list). ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Use `application/json` for text only. Use `multipart/form-data` for media. Generate one random value for this reply. Reuse it only when retrying identical text and attachments. A replay returns the original message with `Idempotency-Replayed: true`. ## Body Message content. 1-10,000 characters. Up to 4 JPEG, PNG, GIF, WebP, MP4, MOV, or WebM files. The same per-file and 30 MB combined limits apply. For a media-only reply, omit `body` and include at least 1 attachment. ```bash cURL With Media theme={null} curl -X POST https://xquik.com/api/v1/support/tickets/tkt_a1b2c3d4e5f6a1b2c3d4e5f6/messages \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: replace-with-one-random-value" \ -F "body=This recording shows the current behavior." \ -F "attachments=@recording.mp4" | jq ``` ## Response ### 201 Created Ticket public ID the message was added to. Created media receipts. Private attachment public ID. Upload status: `ready` or `failed`. ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "attachments": [] } ``` ### 200 Replayed Returns the original response after a safe retry. The `Idempotency-Replayed` response header is `true`. ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` Missing or invalid `body` field. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` No ticket exists with this ID, or it belongs to a different account. ### 409 Conflict The `Idempotency-Key` already belongs to different text or attachments. Generate a new key for the changed reply. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before adding another reply. **Related:** [Support Media](/api-reference/support/media) explains private downloads and upload limits. # Xquik Support Ticket API | Update Request Status Source: https://docs.xquik.com/api-reference/support/update PATCH /support/tickets/{id} Resolve or reopen a support ticket by ID. Preserve its messages and attachments while changing the ticket workflow status. Includes exact API examples. ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "status": "resolved" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Support tickets are free for all authenticated users. ```bash cURL theme={null} curl -X PATCH https://xquik.com/api/v1/support/tickets/tkt_a1b2c3d4e5f6a1b2c3d4e5f6 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "status": "resolved" }' | jq ``` ```javascript Node.js theme={null} const ticketId = "tkt_a1b2c3d4e5f6a1b2c3d4e5f6"; const response = await fetch(`https://xquik.com/api/v1/support/tickets/${ticketId}`, { method: "PATCH", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ status: "resolved", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests ticket_id = "tkt_a1b2c3d4e5f6a1b2c3d4e5f6" response = requests.patch( f"https://xquik.com/api/v1/support/tickets/{ticket_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"status": "resolved"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { ticketID := "tkt_a1b2c3d4e5f6a1b2c3d4e5f6" body, _ := json.Marshal(map[string]interface{}{ "status": "resolved", }) req, err := http.NewRequest("PATCH", "https://xquik.com/api/v1/support/tickets/"+ticketID, bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Path parameters The ticket public ID (e.g. `tkt_a1b2c3d4e5f6a1b2c3d4e5f6`). Returned when you [create a ticket](/api-reference/support/create) or [list tickets](/api-reference/support/list). ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body New status. One of: `open`, `resolved`, `closed`. The `in_progress` status is set by support staff and cannot be set via the API. ## Response ### 200 OK Unique ticket public ID. Updated ticket status. ```json theme={null} { "publicId": "tkt_a1b2c3d4e5f6a1b2c3d4e5f6", "status": "resolved" } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` Missing or invalid `status` value. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Ticket not found." } ``` No support ticket matches the supplied public ID. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before updating ticket status again. **Related:** [Get Ticket](/api-reference/support/get) to fetch ticket details and message history, or [Reply to Ticket](/api-reference/support/reply) to add a message. # Twitter Trends API for WOEID Topics & Hashtags Source: https://docs.xquik.com/api-reference/trends/list GET /trends Use the Twitter Trends API to get ranked topics and hashtags by WOEID. Return search queries, ranks, public post counts, URLs, and regional result totals. ```json theme={null} { "trends": [ { "name": "#AI", "description": "Artificial intelligence discussions", "promotedContent": null, "query": "%23AI", "rank": 1 } ], "total": 30, "woeid": 1 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
## Use the Top-Level Twitter Trends API `GET /trends` is Xquik's top-level alias for ranked WOEID topics. It returns `total`; `/x/trends` returns `count` instead. Keep one response shape throughout each client. **3 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Direct [MPP](/mpp/machine-payments-protocol): USD 0.00045 per call ```bash cURL theme={null} curl "https://xquik.com/api/v1/trends?woeid=23424977&count=10" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const regionWoeid = "23424977"; const requestedCount = "10"; const params = new URLSearchParams({ woeid: regionWoeid, count: requestedCount }); const response = await fetch(`https://xquik.com/api/v1/trends?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const returnedTotal = data.trends.length; const trendRows = data.trends.map((trend) => ({ trend_name: trend.name, rank: trend.rank ?? null, description: trend.description ?? null, search_query: trend.query ?? trend.name, region_woeid: data.woeid, requested_count: Number(requestedCount), returned_total: returnedTotal, })); for (const row of trendRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } ``` ```python Python theme={null} import json import requests region_woeid = 23424977 requested_count = 10 response = requests.get( "https://xquik.com/api/v1/trends", params={"woeid": region_woeid, "count": requested_count}, headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() returned_total = len(data["trends"]) trend_rows = [ { "trend_name": trend["name"], "rank": trend.get("rank"), "description": trend.get("description"), "search_query": trend.get("query", trend["name"]), "region_woeid": data["woeid"], "requested_count": requested_count, "returned_total": returned_total, } for trend in data["trends"] ] for row in trend_rows: print(json.dumps(row)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type Trend struct { Name string `json:"name"` Rank *int `json:"rank,omitempty"` Description *string `json:"description,omitempty"` Query *string `json:"query,omitempty"` } type TrendsResponse struct { Trends []Trend `json:"trends"` Total int `json:"total"` Woeid int `json:"woeid"` } type TrendRow struct { TrendName string `json:"trend_name"` Rank *int `json:"rank"` Description *string `json:"description"` SearchQuery string `json:"search_query"` RegionWoeid int `json:"region_woeid"` RequestedCount int `json:"requested_count"` ReturnedTotal int `json:"returned_total"` } func main() { const requestedCount = 10 req, err := http.NewRequest("GET", "https://xquik.com/api/v1/trends?woeid=23424977&count=10", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_your_api_key_here") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var data TrendsResponse if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { log.Fatal(err) } returnedTotal := len(data.Trends) encoder := json.NewEncoder(os.Stdout) for _, trend := range data.Trends { searchQuery := trend.Name if trend.Query != nil { searchQuery = *trend.Query } if err := encoder.Encode(TrendRow{ TrendName: trend.Name, Rank: trend.Rank, Description: trend.Description, SearchQuery: searchQuery, RegionWoeid: data.Woeid, RequestedCount: requestedCount, ReturnedTotal: returnedTotal, }); err != nil { log.Fatal(err) } } } ``` ## Build a Regional Twitter Trending Monitor Use `GET /trends` for regional dashboards, alerts, queues, warehouses, or agents. The examples emit one JSON line per trend. Store `trend_name`, `rank`, `description`, and `search_query`. Record `region_woeid` and `requested_count` with each snapshot. Record `returned_total` beside those request fields. Map `name` and `query` to the two trend text fields. Map `woeid` and request `count` to the two request fields. Derive `returned_total` from `trends.length`. The raw `total` counts valid trends before `count` slicing. Pass `search_query` to [Search Tweets](/api-reference/x/search-tweets). That search finds related tweets, hashtags, or brand mentions. A Twitter API trends client saves each snapshot before searching. A Twitter API trending monitor compares ranks within one WOEID. Keep missing optional fields unset. Preserve null `tweetVolume` values. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` authenticates `paid_reads` guest keys. Direct MPP uses the `Payment ...` credential. Get it from the `WWW-Authenticate: Payment` challenge. ## Query Parameters Region WOEID. See supported regions below. Omit it to use `1` for Worldwide. Number of trends to return. Max `50`, default `30`. ## Response ### 200 OK Ranked topic records. **Trend object fields:** Trend name or hashtag. Optional trend description. Optional ranking position. Optional tweet-search query. Optional promotion ID; null for organic trends. Optional public post count. Optional trend search URL. Valid trend count before `count` slicing. Requested region WOEID. ```json theme={null} { "trends": [ { "name": "#SuperBowl", "description": "Trending in United States", "rank": 1, "query": "%23SuperBowl", "promotedContent": null, "tweetVolume": 250000, "url": "https://x.com/search?q=%23SuperBowl" }, { "name": "Taylor Swift", "rank": 2, "query": "%22Taylor%20Swift%22" } ], "total": 50, "woeid": 1 } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input" } ``` Use a supported WOEID. Invalid `count` values fall back to `30`. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Replace the missing or invalid API key. ### 402 Payment Required Account keys get account options. Guest keys get a top-up option. Anonymous direct MPP shows two payment choices. Choose either the payment challenge or the guest wallet action. No checkout starts automatically. ### 502 X API Unavailable ```json theme={null} { "error": "x_api_unavailable" } ``` The read service failed. Retry after a short delay. ### 429 Rate Limit Exceeded ```json theme={null} { "error": "rate_limit_exceeded", "retryAfter": 60 } ``` Wait for `Retry-After` before retrying. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` The opt-in normalized contract returns 424 when the read service fails. Send `xquik-api-contract: 2026-04-29` to opt in. Default v1 returns 502. ## Twitter Trends API Questions ### How Do I Get Twitter Trends Programmatically? Call `GET /trends` with a WOEID and result count. Read each returned topic field described above. ### Can I Get Location-Based Trending Topics? Yes. Use a listed WOEID and keep it with each rank snapshot. ### Does the API Return Historical Twitter Trends? No. Each call returns one regional snapshot. Save timestamped snapshots on the schedule you control. ### How Do I Authenticate to the Twitter Trends API? Send an account key, OAuth token, guest key, or direct MPP credential. ### Is the Twitter Trends API Free? No. The pricing callout shows each successful call's cost. ### Does This Replace X's Official API? No. This page documents Xquik, an independent third-party service. It does not document X's official trends endpoint. ## Regions Use these WOEIDs in the `woeid` query parameter. Use `1` for Worldwide. * `1` - Worldwide * `23424977` - United States * `23424775` - Canada * `23424900` - Mexico * `23424768` - Brazil * `23424975` - United Kingdom * `23424969` - Turkey * `23424950` - Spain * `23424829` - Germany * `23424819` - France * `23424856` - Japan * `23424848` - India **Related:** [Billing & Usage](/guides/billing) · [Search Tweets](/api-reference/x/search-tweets) # Twitter Webhook API | Create Signed Endpoint Source: https://docs.xquik.com/api-reference/webhooks/create POST /webhooks Register an HTTPS endpoint for signed tweet, follower, profile, relationship, or keyword monitor events with selected filters. Includes request fields. ```json theme={null} { "id": "42", "url": "https://example.com/webhook", "secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "eventTypes": [ "tweet.new" ], "createdAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/webhooks \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new", "tweet.reply"] }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/webhooks", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://your-server.com/webhook", eventTypes: ["tweet.new", "tweet.reply"], }), }); const webhook = await response.json(); const webhookSecret = webhook.secret; // Store webhookSecret in your secret manager; do not print it in logs. ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/webhooks", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new", "tweet.reply"], }, ) webhook = response.json() webhook_secret = webhook["secret"] # Store webhook_secret in your secret manager; do not print it in logs. ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "log" "net/http" ) func main() { payload := map[string]interface{}{ "url": "https://your-server.com/webhook", "eventTypes": []string{"tweet.new", "tweet.reply"}, } body, err := json.Marshal(payload) if err != nil { log.Fatal(err) } req, err := http.NewRequest("POST", "https://xquik.com/api/v1/webhooks", bytes.NewReader(body)) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var webhook map[string]any if err := json.NewDecoder(resp.Body).Decode(&webhook); err != nil { log.Fatal(err) } secret, _ := webhook["secret"].(string) // Store secret in your secret manager; do not print it in logs. _ = secret } ``` ## Headers Your API key. Session cookie authentication is also supported. Must be `application/json`. ## Body HTTPS endpoint URL where events will be delivered. HTTP URLs are rejected. URLs resolving to private or internal IP addresses (localhost, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x) are also rejected. Array of event types to subscribe to. At least 1 required. Use any valid account monitor event type listed below. Keyword monitors emit only `tweet.*` event types. Account monitors can emit both `tweet.*` and `profile.*` event types. ## Valid event types Valid types: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, `tweet.longform`, `profile.avatar.changed`, `profile.banner.changed`, `profile.name.changed`, `profile.username.changed`, `profile.bio.changed`, `profile.location.changed`, `profile.url.changed`, `profile.verified.changed`, `profile.protected.changed`, `profile.pinned_tweet.changed`, `profile.unavailable.changed`. Original tweet from an account monitor or matching keyword monitor. Use for new posts that are not replies, quotes, or retweets. Quote tweet from an account monitor or matching keyword monitor. Pair with monitors that include `tweet.quote`. Reply from an account monitor or matching keyword monitor. Pair with monitors that include `tweet.reply`. Retweet from an account monitor or matching keyword monitor. Pair with monitors that include `tweet.retweet`. `webhook.test` is sent only by the [Test Webhook](/api-reference/webhooks/test) endpoint. It is not a subscribable `eventTypes` value. ## Integration handoff Use this endpoint after creating an account monitor with [`POST /monitors`](/api-reference/monitors/create) or a keyword monitor with [`POST /monitors/keywords`](/api-reference/monitors/create-keyword). The webhook stores the HTTPS endpoint and event-type filter. Active monitors produce the events; webhook delivery is included with monitor billing. Store these fields immediately after creation: Store `id` for `POST /webhooks/{id}/test`, updates, deletes, and delivery lookups. Store `url` to audit which queue, CRM, warehouse, or app endpoint receives monitor events. Store `eventTypes`; keep the webhook filter aligned with the account or keyword monitor event types. Store `secret` once and use it to verify `X-Xquik-Signature` on the raw request body. Store `createdAt` for audit logs and configuration drift checks. Expect HTTPS `POST` bodies with `eventType`, `schemaVersion`, `deliveryId`, `streamEventId`, `occurredAt`, `data`, plus `username` for account monitor events or `query` for keyword monitor events. Every delivery includes `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` headers. Use `deliveryId` as the per-endpoint idempotency key and `streamEventId` when one monitor event must be processed once across retries or endpoints. Test the endpoint with [`POST /webhooks/{id}/test`](/api-reference/webhooks/test) before routing production events. Return a `2xx` response within 10 seconds, then process slow Slack, CRM, warehouse, or queue work asynchronously. Use [Signature Verification](/webhooks/verification) to validate the raw request body before processing. ## Response ### 201 Created Unique webhook identifier. The registered delivery endpoint. Event types this webhook is subscribed to. HMAC signing secret (64-character hex string). Store securely. Returned only at creation time. ISO 8601 creation timestamp. ```json theme={null} { "id": "15", "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new", "tweet.reply"], "secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "createdAt": "2026-02-24T10:30:00.000Z" } ``` The `secret` is returned **only once**. Store it securely. You need it to [verify webhook signatures](/webhooks/verification). It cannot be retrieved again. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key / session cookie. ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid URL or event types" } ``` Invalid URL (must be HTTPS) or empty `eventTypes` array. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. This endpoint supports **dual authentication**: API key (`x-api-key` header) or session cookie from the dashboard. **Next steps:** [List Webhooks](/api-reference/webhooks/list) · [Signature Verification](/webhooks/verification) · [Webhooks Overview](/webhooks/overview) # Delete Twitter Webhook Endpoint & Stop Events Source: https://docs.xquik.com/api-reference/webhooks/delete DELETE /webhooks/{id} Delete a webhook endpoint and stop future tweet, follower, profile, relationship, or keyword monitor event deliveries to its URL. See response fields. ```json theme={null} { "success": true } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/webhooks/15 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq --arg webhook_id "15" '{ webhook_id: $webhook_id, success, inventory_endpoint: "/api/v1/webhooks", deliveries_endpoint: ("/api/v1/webhooks/" + $webhook_id + "/deliveries") }' ``` ```javascript Node.js theme={null} const webhookId = "15"; const response = await fetch(`https://xquik.com/api/v1/webhooks/${webhookId}`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const result = await response.json(); const deactivationHandoff = { webhook_id: webhookId, success: result.success === true, inventory_endpoint: "/api/v1/webhooks", deliveries_endpoint: `/api/v1/webhooks/${webhookId}/deliveries`, }; ``` ```python Python theme={null} import requests webhook_id = "15" response = requests.delete( f"https://xquik.com/api/v1/webhooks/{webhook_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) result = response.json() deactivation_handoff = { "webhook_id": webhook_id, "success": result["success"] is True, "inventory_endpoint": "/api/v1/webhooks", "deliveries_endpoint": f"/api/v1/webhooks/{webhook_id}/deliveries", } ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" ) type DeleteResult struct { Success bool `json:"success"` } type DeactivationHandoff struct { DeliveriesEndpoint string `json:"deliveries_endpoint"` InventoryEndpoint string `json:"inventory_endpoint"` Success bool `json:"success"` WebhookID string `json:"webhook_id"` } func main() { webhookID := "15" req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/webhooks/"+webhookID, nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result DeleteResult if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { log.Fatal(err) } handoff := DeactivationHandoff{ DeliveriesEndpoint: "/api/v1/webhooks/" + webhookID + "/deliveries", InventoryEndpoint: "/api/v1/webhooks", Success: result.Success, WebhookID: webhookID, } _ = handoff } ``` These snippets shape a deactivation receipt. Store the webhook ID with its inventory and delivery-log endpoints instead of printing the raw delete response. ## What delete does `DELETE /webhooks/{id}` soft-deactivates the endpoint by setting `isActive` to `false`. The webhook record stays available in [List Webhooks](/api-reference/webhooks/list), and previous delivery rows stay available in [List Deliveries](/api-reference/webhooks/deliveries). The response is only `{ "success": true }`; it does not return the URL, event types, or signing secret. To receive events again, call [Update Webhook](/api-reference/webhooks/update) with `isActive: true`, then run [Test Webhook](/api-reference/webhooks/test) before routing production monitor events to the receiver. ## Path parameters The webhook ID to deactivate. ## Headers Your API key. This endpoint also accepts session cookie authentication. ## Response ### 200 OK Always `true` on successful deactivation. ```json theme={null} { "success": true } ``` The webhook is deactivated immediately. Pending deliveries that are already in-flight may still be attempted, but no new deliveries will be queued. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key / session cookie. ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid webhook ID format" } ``` The provided webhook ID is not a valid format. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Webhook not found" } ``` No webhook exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Deactivation handoff Use this endpoint when a receiver should stop getting future monitor events but you still need the webhook record and delivery history for audit or rollback. The endpoint sets `isActive` to `false` and returns `{ "success": true }`. It does not return the webhook configuration. Inactive webhooks do not receive new monitor deliveries or scheduled retries. In-flight delivery attempts may still finish. Use [List Webhooks](/api-reference/webhooks/list) to confirm `webhooks[].isActive: false`, then use [List Deliveries](/api-reference/webhooks/deliveries) for prior delivery status, attempts, errors, and timestamps. Delivery rows for inactive endpoints can show `pending`, `failed`, or `exhausted` attempts. Store `streamEventId`, `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`. Use delivery `streamEventId` as the `{id}` for [Get Event](/api-reference/events/get). Store `monitorId`, `monitorType`, `type`, `occurredAt`, and `data` with the deactivation record. Remove queue, CRM, alerting, or warehouse routing only after the endpoint is inactive and no more production events are expected. To reuse the same webhook, call [Update Webhook](/api-reference/webhooks/update) with `isActive: true`, then run [Test Webhook](/api-reference/webhooks/test). Delete returns no `secret`. Keep the original Create Webhook secret for audit records or future reactivation. This endpoint supports **dual authentication**: API key (`x-api-key` header) or session cookie from the dashboard. **Related:** [List Webhooks](/api-reference/webhooks/list) · [Update Webhook](/api-reference/webhooks/update) · [Test Webhook](/api-reference/webhooks/test) · [List Deliveries](/api-reference/webhooks/deliveries) · [Get Event](/api-reference/events/get) · [Create Webhook](/api-reference/webhooks/create) # Twitter Webhook Deliveries & Retry History Source: https://docs.xquik.com/api-reference/webhooks/deliveries GET /webhooks/{id}/deliveries Inspect a webhook's tweet, follower, profile, relationship, or keyword event delivery attempts, HTTP statuses, errors, and retry times. See event fields. ```json theme={null} { "deliveries": [] } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl https://xquik.com/api/v1/webhooks/15/deliveries \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '[.deliveries[] | { delivery_id: .id, stream_event_id: .streamEventId, status, attempts, receiver_status: (.lastStatusCode // null), last_error: (.lastError // null), queued_at: .createdAt, delivered_at: (.deliveredAt // null), action: ( if .status == "exhausted" then "page" elif .status == "failed" and .attempts >= 3 then "warn" else "track" end ) }]' ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/webhooks/15/deliveries", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, } ); const data = await response.json(); const deliveryTriage = data.deliveries.map((delivery) => ({ delivery_id: delivery.id, stream_event_id: delivery.streamEventId, status: delivery.status, attempts: delivery.attempts, receiver_status: delivery.lastStatusCode ?? null, last_error: delivery.lastError ?? null, queued_at: delivery.createdAt, delivered_at: delivery.deliveredAt ?? null, action: delivery.status === "exhausted" ? "page" : delivery.status === "failed" && delivery.attempts >= 3 ? "warn" : "track", })); const retryCandidates = deliveryTriage.filter((row) => row.action !== "track"); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/webhooks/15/deliveries", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() delivery_triage = [ { "delivery_id": delivery["id"], "stream_event_id": delivery["streamEventId"], "status": delivery["status"], "attempts": delivery["attempts"], "receiver_status": delivery.get("lastStatusCode"), "last_error": delivery.get("lastError"), "queued_at": delivery["createdAt"], "delivered_at": delivery.get("deliveredAt"), "action": "page" if delivery["status"] == "exhausted" else "warn" if delivery["status"] == "failed" and delivery["attempts"] >= 3 else "track", } for delivery in data["deliveries"] ] retry_candidates = [ row for row in delivery_triage if row["action"] != "track" ] ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" ) type Delivery struct { ID string `json:"id"` StreamEventID string `json:"streamEventId"` Status string `json:"status"` Attempts int `json:"attempts"` LastStatusCode *int `json:"lastStatusCode"` LastError *string `json:"lastError"` CreatedAt string `json:"createdAt"` DeliveredAt *string `json:"deliveredAt"` } type DeliveriesResponse struct { Deliveries []Delivery `json:"deliveries"` } type DeliveryTriage struct { DeliveryID string `json:"delivery_id"` StreamEventID string `json:"stream_event_id"` Status string `json:"status"` Attempts int `json:"attempts"` ReceiverStatus *int `json:"receiver_status"` LastError *string `json:"last_error"` QueuedAt string `json:"queued_at"` DeliveredAt *string `json:"delivered_at"` Action string `json:"action"` } func actionForDelivery(delivery Delivery) string { if delivery.Status == "exhausted" { return "page" } if delivery.Status == "failed" && delivery.Attempts >= 3 { return "warn" } return "track" } func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/webhooks/15/deliveries", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var data DeliveriesResponse if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { log.Fatal(err) } deliveryTriage := make([]DeliveryTriage, 0, len(data.Deliveries)) for _, delivery := range data.Deliveries { row := DeliveryTriage{ DeliveryID: delivery.ID, StreamEventID: delivery.StreamEventID, Status: delivery.Status, Attempts: delivery.Attempts, ReceiverStatus: delivery.LastStatusCode, LastError: delivery.LastError, QueuedAt: delivery.CreatedAt, DeliveredAt: delivery.DeliveredAt, Action: actionForDelivery(delivery), } deliveryTriage = append(deliveryTriage, row) } retryCandidates := make([]DeliveryTriage, 0) for _, row := range deliveryTriage { if row.Action != "track" { retryCandidates = append(retryCandidates, row) } } _ = retryCandidates } ``` ## Path parameters The webhook ID to retrieve deliveries for. ## Headers Your API key. ## Operational handoff Use this endpoint when your queue, CRM, warehouse, or alerting system needs to reconcile webhook delivery health. It returns the 100 most recent delivery records for one webhook, newest first. Map each delivery into a small incident row before sending it downstream. Keep `id`, `streamEventId`, `status`, `attempts`, receiver status, timestamps, and the chosen action; avoid dumping the full response into logs. This endpoint returns delivery attempt metadata only. It does not return the webhook URL, event type filter, signing secret, raw payload body, raw signature, or full request headers. Do not depend on a `nextRetryAt` field. The response does not expose one; route incidents from `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`. | Webhook delivery incident column | Response source | Triage rule | | -------------------------------- | ----------------------------- | ---------------------------------------------------- | | Delivery ID | `deliveries[].id` | Use this ID for idempotency and support lookup. | | Monitor event ID | `deliveries[].streamEventId` | Join the delivery to `GET /events/{id}`. | | Delivery state | `deliveries[].status` | Route pending, failed, exhausted, or delivered rows. | | Attempt count | `deliveries[].attempts` | Identify early, repeated, or terminal failures. | | Receiver status | `deliveries[].lastStatusCode` | Separate HTTP failures from unreachable receivers. | | Failure reason | `deliveries[].lastError` | Show the latest receiver error to operators. | | Delivery timing | `createdAt` and `deliveredAt` | Measure queue and recovery latency. | Store these fields for support and retry triage: Store `id` for delivery-level idempotency and support lookup. Store `streamEventId` to join back to the stored monitor event with `GET /events/{id}`. Use `status` to route `pending`, `failed`, and `exhausted` deliveries to the right queue. Use `attempts` to decide whether a failed delivery is early, repeated, or at the retry cap. Use `lastStatusCode` to separate receiver errors such as `500` from unreachable endpoints with status `0`. Show `lastError` as the most recent failure reason for the operator. Compare `createdAt` and `deliveredAt` to measure delivery latency and recovery time. ## Incident response handoff Use one compact handoff row when a delivery needs receiver-owner action. Route `delivered` rows out of the incident path, track `pending`, warn on repeated `failed`, and page on `exhausted`. ```json theme={null} { "record_type": "webhook_delivery_incident_handoff", "webhook_id": "15", "delivery_id": "503", "stream_event_id": "9003", "status": "exhausted", "attempts": 1, "receiver_status": 410, "last_error": "HTTP 410", "terminal_reason": "receiver_returned_410", "event_join": "GET /api/v1/events/9003", "verification_check": "POST /api/v1/webhooks/15/test", "action": "page_receiver_owner", "handoff_state": "fix_receiver_then_send_signed_test" } ``` Warn after repeated `failed` rows. Include `attempts`, `lastStatusCode`, and `lastError` so the receiver owner can separate code errors from reachability failures. Page when `status` is `exhausted`. This means retryable failures reached the 10-attempt cap, or the receiver returned `410 Gone`. Store `streamEventId` and link `GET /api/v1/events/{id}` so support can inspect the monitor event that triggered the delivery. After fixing the endpoint, send `POST /webhooks/{id}/test` and attach the signed test result to the incident before waiting for the next event. Failures retry up to 10 attempts with exponential backoff, starting at 1 second and capped at 60 seconds. A `410 Gone` response marks the delivery `exhausted` immediately. Other non-`2xx` responses and network failures stay `failed` until they are delivered or exhaust all attempts. For incident response, page on `exhausted`, warn on repeated `failed`, and ignore `delivered`. Fix the receiving endpoint, then use [`POST /webhooks/{id}/test`](/api-reference/webhooks/test) to confirm it accepts signed requests before waiting for the next production event. ### Receiver backfill handoff This endpoint returns the latest 100 delivery rows for one webhook. Use those rows to identify receiver failures, then use stored event pages to rebuild your own downstream queue after the receiver is fixed. ```json theme={null} { "record_type": "webhook_delivery_backfill_handoff", "delivery_source": "GET /api/v1/webhooks/15/deliveries", "event_source": "GET /api/v1/events?limit=100&cursor={nextCursor}", "source_filter": "monitorId for account monitors, keywordMonitorId for keyword monitors", "join_key": "delivery.streamEventId == event.id", "store": [ "deliveryId", "streamEventId", "status", "attempts", "eventId", "nextCursor" ], "stop_when": "hasMore is false", "handoff_state": "receiver_fixed_backfill_events_then_resume_webhooks" } ``` Store `nextCursor` after every event page. Continue `GET /api/v1/events?limit=100&cursor={nextCursor}` until `hasMore` is `false`, then compare each event `id` with delivery `streamEventId` values before replaying your own downstream work. Add `monitorId` when replaying one account monitor, or `keywordMonitorId` when replaying one keyword monitor. ## Response ### 200 OK List of delivery attempts, most recent first. Returns up to 100 deliveries. **Delivery object fields:** Unique delivery identifier. ID of the stream event that triggered this delivery. Current delivery status: `pending`, `delivered`, `failed`, or `exhausted`. Total number of delivery attempts made. HTTP status code returned by your endpoint on the most recent attempt. Omitted if no attempt has been made yet. Error message from the most recent failed attempt. Omitted on success. ISO 8601 timestamp of when the delivery was queued. ISO 8601 timestamp of successful delivery. Omitted if not yet delivered. ```json theme={null} { "deliveries": [ { "id": "501", "streamEventId": "9001", "status": "delivered", "attempts": 1, "lastStatusCode": 200, "createdAt": "2026-02-24T14:22:01.000Z", "deliveredAt": "2026-02-24T14:22:02.000Z" }, { "id": "502", "streamEventId": "9002", "status": "failed", "attempts": 3, "lastStatusCode": 500, "lastError": "HTTP 500", "createdAt": "2026-02-24T14:25:00.000Z" }, { "id": "503", "streamEventId": "9003", "status": "exhausted", "attempts": 10, "lastStatusCode": 503, "lastError": "HTTP 503", "createdAt": "2026-02-24T14:30:00.000Z" }, { "id": "504", "streamEventId": "9004", "status": "pending", "attempts": 0, "createdAt": "2026-02-24T14:35:00.000Z" } ] } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. Check the `x-api-key` header value. ### 400 Invalid ID ```json theme={null} { "error": "invalid_id" } ``` The provided webhook ID is not a valid format. ### 404 Not Found ```json theme={null} { "error": "not_found" } ``` No webhook exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Delivery statuses Delivery is queued and waiting for the next attempt. Your endpoint returned `2xx`. Delivery is complete. The most recent attempt failed because the endpoint returned non-`2xx` or the network request failed. Xquik retries with exponential backoff. All retry attempts have been used. Xquik will not retry this delivery. Check the receiver endpoint, then use [Resume Webhook](/api-reference/webhooks/resume) if the webhook needs attention before new deliveries continue. Deliveries follow an exponential backoff retry schedule. After each failed attempt, the wait time increases. Once all retries are exhausted, the status transitions to `exhausted` and no further attempts are made. **Related:** [Webhooks Overview](/webhooks/overview) · [Resume Webhook](/api-reference/webhooks/resume) · [Webhook Testing Guide](/guides/twitter-webhook-testing) # List Twitter Webhook Endpoints & Event Filters Source: https://docs.xquik.com/api-reference/webhooks/list GET /webhooks List registered webhook endpoints with URLs, event filters, active states, failure counts, delivery timestamps, and creation times. See request fields. ```json theme={null} { "webhooks": [] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl https://xquik.com/api/v1/webhooks \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '.webhooks[] | { webhook_id: .id, url, event_types: .eventTypes, is_active: .isActive, delivery_status: .deliveryStatus, consecutive_failures: .consecutiveFailures, failure_hard_cap: .failureHardCap, created_at: .createdAt, update_endpoint: ("/api/v1/webhooks/" + .id), delete_endpoint: ("/api/v1/webhooks/" + .id), test_endpoint: ("/api/v1/webhooks/" + .id + "/test"), resume_endpoint: ("/api/v1/webhooks/" + .id + "/resume"), deliveries_endpoint: ("/api/v1/webhooks/" + .id + "/deliveries"), signing_secret_available: false }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/webhooks", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const data = await response.json(); const webhookRows = data.webhooks.map((webhook) => ({ webhook_id: webhook.id, url: webhook.url, event_types: webhook.eventTypes, is_active: webhook.isActive, delivery_status: webhook.deliveryStatus, consecutive_failures: webhook.consecutiveFailures, failure_hard_cap: webhook.failureHardCap, created_at: webhook.createdAt, update_endpoint: `/api/v1/webhooks/${webhook.id}`, delete_endpoint: `/api/v1/webhooks/${webhook.id}`, test_endpoint: `/api/v1/webhooks/${webhook.id}/test`, resume_endpoint: `/api/v1/webhooks/${webhook.id}/resume`, deliveries_endpoint: `/api/v1/webhooks/${webhook.id}/deliveries`, signing_secret_available: false, })); for (const row of webhookRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } ``` ```python Python theme={null} import json import requests response = requests.get( "https://xquik.com/api/v1/webhooks", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() webhook_rows = [ { "webhook_id": webhook["id"], "url": webhook["url"], "event_types": webhook["eventTypes"], "is_active": webhook["isActive"], "delivery_status": webhook["deliveryStatus"], "consecutive_failures": webhook["consecutiveFailures"], "failure_hard_cap": webhook["failureHardCap"], "created_at": webhook["createdAt"], "update_endpoint": f"/api/v1/webhooks/{webhook['id']}", "delete_endpoint": f"/api/v1/webhooks/{webhook['id']}", "test_endpoint": f"/api/v1/webhooks/{webhook['id']}/test", "resume_endpoint": f"/api/v1/webhooks/{webhook['id']}/resume", "deliveries_endpoint": f"/api/v1/webhooks/{webhook['id']}/deliveries", "signing_secret_available": False, } for webhook in data["webhooks"] ] for row in webhook_rows: print(json.dumps(row)) ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" "os" ) type Webhook struct { ConsecutiveFailures int `json:"consecutiveFailures"` CreatedAt string `json:"createdAt"` DeliveryStatus string `json:"deliveryStatus"` EventTypes []string `json:"eventTypes"` FailureHardCap int `json:"failureHardCap"` ID string `json:"id"` IsActive bool `json:"isActive"` URL string `json:"url"` } type WebhookResponse struct { Webhooks []Webhook `json:"webhooks"` } type WebhookRow struct { WebhookID string `json:"webhook_id"` URL string `json:"url"` EventTypes []string `json:"event_types"` IsActive bool `json:"is_active"` DeliveryStatus string `json:"delivery_status"` ConsecutiveFailures int `json:"consecutive_failures"` FailureHardCap int `json:"failure_hard_cap"` CreatedAt string `json:"created_at"` UpdateEndpoint string `json:"update_endpoint"` DeleteEndpoint string `json:"delete_endpoint"` TestEndpoint string `json:"test_endpoint"` ResumeEndpoint string `json:"resume_endpoint"` DeliveriesEndpoint string `json:"deliveries_endpoint"` SigningSecretAvailable bool `json:"signing_secret_available"` } func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/webhooks", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var data WebhookResponse if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { log.Fatal(err) } encoder := json.NewEncoder(os.Stdout) for _, webhook := range data.Webhooks { row := WebhookRow{ WebhookID: webhook.ID, URL: webhook.URL, EventTypes: webhook.EventTypes, IsActive: webhook.IsActive, DeliveryStatus: webhook.DeliveryStatus, ConsecutiveFailures: webhook.ConsecutiveFailures, FailureHardCap: webhook.FailureHardCap, CreatedAt: webhook.CreatedAt, UpdateEndpoint: "/api/v1/webhooks/" + webhook.ID, DeleteEndpoint: "/api/v1/webhooks/" + webhook.ID, TestEndpoint: "/api/v1/webhooks/" + webhook.ID + "/test", ResumeEndpoint: "/api/v1/webhooks/" + webhook.ID + "/resume", DeliveriesEndpoint: "/api/v1/webhooks/" + webhook.ID + "/deliveries", SigningSecretAvailable: false, } if err := encoder.Encode(row); err != nil { log.Fatal(err) } } } ``` These examples shape one inventory row per webhook instead of printing the full response page. Split the rows by `is_active` and `delivery_status`, then store update, delete, test, resume, and delivery-log endpoints with each receiver. List responses never include the signing secret; keep the one-time `secret` from [Create Webhook](/api-reference/webhooks/create) in your secret manager. ## Headers Your API key. Session cookie authentication is also supported. ## Response ### 200 OK List of webhook objects for your account. Returns up to 200 webhooks. **Webhook object fields:** Unique webhook identifier. Delivery endpoint URL. Event types this webhook is subscribed to. Whether the webhook is currently active. Consecutive delivery failures recorded for this webhook. `active`, `paused`, or `needs_attention`. Failure count where the webhook needs attention before resuming. ISO 8601 creation timestamp. ```json theme={null} { "webhooks": [ { "id": "15", "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true, "consecutiveFailures": 0, "deliveryStatus": "active", "failureHardCap": 200, "createdAt": "2026-02-24T10:30:00.000Z" }, { "id": "16", "url": "https://backup.example.com/xquik", "eventTypes": ["tweet.new"], "isActive": false, "consecutiveFailures": 200, "deliveryStatus": "needs_attention", "failureHardCap": 200, "createdAt": "2026-02-20T08:15:00.000Z" } ] } ``` The `secret` is **never** included in list responses. It is only returned once at creation time. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key / session cookie. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Inventory handoff Use this endpoint when an ops job, CRM integration, alert worker, or agent needs to reconcile which signed webhook endpoints are configured for the account. | Webhook inventory column | Response source | Reconciliation rule | | ------------------------ | -------------------------------- | ----------------------------------------------- | | Webhook ID | `webhooks[].id` | Use this ID for updates, tests, and deliveries. | | Receiver URL | `webhooks[].url` | Confirm the intended HTTPS destination. | | Event filter | `webhooks[].eventTypes` | Match account and keyword monitor event types. | | Active state | `webhooks[].isActive` | Separate active and inactive delivery targets. | | Delivery status | `webhooks[].deliveryStatus` | Route needs-attention receivers for recovery. | | Consecutive failures | `webhooks[].consecutiveFailures` | Alert before the receiver reaches its hard cap. | | Failure cap | `webhooks[].failureHardCap` | Record the recovery threshold. | | Creation time | `webhooks[].createdAt` | Detect configuration age and drift. | Store `webhooks[].id` for updates, deletes, test deliveries, and delivery history lookups. Store `webhooks[].url` so configuration reviews can detect stale receiver endpoints before production monitor events fail. Store `webhooks[].eventTypes` and compare it with monitor event types before expecting `tweet.new`, `tweet.quote`, `tweet.reply`, or `tweet.retweet`. Store `webhooks[].isActive`; inactive webhooks do not receive monitor events. Use `deliveryStatus` to distinguish paused from needs-attention receivers. Store `webhooks[].deliveryStatus`. Route `needs_attention` rows to [Resume Webhook](/api-reference/webhooks/resume) after the receiver is fixed. Store `webhooks[].consecutiveFailures` and `webhooks[].failureHardCap` for alerting before the receiver reaches the recovery gate. For each row, link active receivers to [Update Webhook](/api-reference/webhooks/update), [Test Webhook](/api-reference/webhooks/test), [Resume Webhook](/api-reference/webhooks/resume), and [List Deliveries](/api-reference/webhooks/deliveries). Link stale receivers to [Delete Webhook](/api-reference/webhooks/delete). Inactive rows are configuration records, not active delivery targets. Keep their `webhooks[].id` so deactivation receipts and rollback plans can find the same webhook. Use the per-webhook deliveries endpoint to store `streamEventId`, `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`. Use delivery `streamEventId` as the `{id}` for [Get Event](/api-reference/events/get). Store `monitorId`, `monitorType`, `type`, `occurredAt`, and `data` with the inventory audit. Store `webhooks[].createdAt` for audit logs and configuration drift checks. The signing `secret` is not listed. Store it from [Create Webhook](/api-reference/webhooks/create), then verify deliveries with [Signature Verification](/webhooks/verification). This endpoint supports **dual authentication**: API key (`x-api-key` header) or session cookie from the dashboard. **Next steps:** [Create Webhook](/api-reference/webhooks/create) · [Update Webhook](/api-reference/webhooks/update) · [Test Webhook](/api-reference/webhooks/test) · [Resume Webhook](/api-reference/webhooks/resume) · [List Deliveries](/api-reference/webhooks/deliveries) · [Get Event](/api-reference/events/get) · [Delete Webhook](/api-reference/webhooks/delete) # Resume Twitter Webhook Deliveries & Retry Events Source: https://docs.xquik.com/api-reference/webhooks/resume POST /webhooks/{id}/resume Test and resume a paused webhook after delivery failures. Confirm its HTTPS response before sending new signed monitor events. Includes request fields. ```json theme={null} { "success": true, "statusCode": 200, "webhook": { "id": "42", "url": "https://example.com/webhook", "eventTypes": [ "tweet.new" ], "isActive": true, "consecutiveFailures": 0 } } ``` ```json theme={null} { "error": "Connection timed out" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/webhooks/15/resume \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq '{ webhook_id: .webhook.id, resumed: (.success == true), status_code: .statusCode, delivery_status: .webhook.deliveryStatus, consecutive_failures: .webhook.consecutiveFailures, failure_hard_cap: .webhook.failureHardCap, test_endpoint: ("/api/v1/webhooks/" + .webhook.id + "/test"), deliveries_endpoint: ("/api/v1/webhooks/" + .webhook.id + "/deliveries") }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/webhooks/15/resume", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const result = await response.json(); const resumeHandoff = { webhook_id: result.webhook.id, resumed: result.success === true, status_code: result.statusCode, delivery_status: result.webhook.deliveryStatus, consecutive_failures: result.webhook.consecutiveFailures, failure_hard_cap: result.webhook.failureHardCap, test_endpoint: `/api/v1/webhooks/${result.webhook.id}/test`, deliveries_endpoint: `/api/v1/webhooks/${result.webhook.id}/deliveries`, }; ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/webhooks/15/resume", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) result = response.json() webhook = result["webhook"] resume_handoff = { "webhook_id": webhook["id"], "resumed": result["success"] is True, "status_code": result["statusCode"], "delivery_status": webhook["deliveryStatus"], "consecutive_failures": webhook["consecutiveFailures"], "failure_hard_cap": webhook["failureHardCap"], "test_endpoint": f"/api/v1/webhooks/{webhook['id']}/test", "deliveries_endpoint": f"/api/v1/webhooks/{webhook['id']}/deliveries", } ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" ) type Webhook struct { ConsecutiveFailures int `json:"consecutiveFailures"` CreatedAt string `json:"createdAt"` DeliveryStatus string `json:"deliveryStatus"` EventTypes []string `json:"eventTypes"` FailureHardCap int `json:"failureHardCap"` ID string `json:"id"` IsActive bool `json:"isActive"` URL string `json:"url"` } type ResumeResponse struct { StatusCode int `json:"statusCode"` Success bool `json:"success"` Webhook Webhook `json:"webhook"` } type ResumeHandoff struct { ConsecutiveFailures int `json:"consecutive_failures"` DeliveriesEndpoint string `json:"deliveries_endpoint"` DeliveryStatus string `json:"delivery_status"` FailureHardCap int `json:"failure_hard_cap"` Resumed bool `json:"resumed"` StatusCode int `json:"status_code"` TestEndpoint string `json:"test_endpoint"` WebhookID string `json:"webhook_id"` } func main() { req, err := http.NewRequest("POST", "https://xquik.com/api/v1/webhooks/15/resume", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result ResumeResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { log.Fatal(err) } handoff := ResumeHandoff{ ConsecutiveFailures: result.Webhook.ConsecutiveFailures, DeliveriesEndpoint: "/api/v1/webhooks/" + result.Webhook.ID + "/deliveries", DeliveryStatus: result.Webhook.DeliveryStatus, FailureHardCap: result.Webhook.FailureHardCap, Resumed: result.Success, StatusCode: result.StatusCode, TestEndpoint: "/api/v1/webhooks/" + result.Webhook.ID + "/test", WebhookID: result.Webhook.ID, } _ = handoff } ``` These snippets shape a recovery row. Store `delivery_status`, `consecutive_failures`, `failure_hard_cap`, `status_code`, and the delivery-log endpoint beside the webhook ID. ## Path parameters The webhook ID to test and resume. ## Headers Your API key. This endpoint also accepts session cookie authentication. ## What happens Xquik sends a signed `webhook.test` request to the webhook URL. If your receiver returns a `2xx` status, Xquik reactivates the webhook, resets `consecutiveFailures` to `0`, and returns the updated webhook object. If the signed test fails, Xquik returns `400` with `success: false`, `statusCode`, and `error`. The webhook is not reactivated. This endpoint does not rotate or return the signing secret. Keep using the one-time secret from [Create Webhook](/api-reference/webhooks/create). | Webhook resume check | Response source | Recovery rule | | -------------------- | ----------------------------- | --------------------------------------- | | Signed test result | `success` | Resume only when this value is `true`. | | Receiver status | `statusCode` | Require a `2xx` response. | | Webhook ID | `webhook.id` | Match the receiver being recovered. | | Delivery URL | `webhook.url` | Confirm the repaired HTTPS endpoint. | | Event filter | `webhook.eventTypes` | Preserve monitor subscription coverage. | | Active state | `webhook.isActive` | Expect `true` after recovery. | | Failure count | `webhook.consecutiveFailures` | Expect `0` after recovery. | | Delivery state | `webhook.deliveryStatus` | Expect `active` after recovery. | ## Response ### 200 OK `true` when the signed test request succeeded and the webhook was resumed. HTTP status code returned by your receiver during the signed test. Updated webhook configuration. **Webhook object fields:** Unique webhook identifier. Delivery endpoint URL. Event types this webhook is subscribed to. `true` after a successful resume. Consecutive receiver failures. Resets to `0` after a successful resume. `active`, `paused`, or `needs_attention`. Failure count where the webhook needs attention before resuming. ISO 8601 creation timestamp. ```json theme={null} { "success": true, "statusCode": 200, "webhook": { "id": "15", "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true, "consecutiveFailures": 0, "deliveryStatus": "active", "failureHardCap": 200, "createdAt": "2026-02-24T10:30:00.000Z" } } ``` ### 400 Test Failed `false` when the signed test did not receive a `2xx` response. HTTP status code from the receiver, or `0` if it was unreachable. Delivery failure reason. ```json theme={null} { "success": false, "statusCode": 500, "error": "HTTP 500" } ``` Fix the receiver, then retry this endpoint. ### 400 Invalid Request ```json theme={null} { "error": "invalid_id", "message": "Invalid webhook ID format" } ``` The provided webhook ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key / session cookie. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Webhook not found" } ``` No webhook exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Recovery handoff Use this endpoint after fixing a receiver that returned repeated non-`2xx` responses, became unreachable, or reached `deliveryStatus: "needs_attention"`. `deliveryStatus: "needs_attention"` means the receiver has reached the failure cap. Fix the receiver before resuming. Resume only succeeds after a signed `webhook.test` request receives a `2xx` response from your receiver. Store `consecutiveFailures` and `failureHardCap` for alerting and recovery dashboards. Resume does not return or rotate `secret`. Keep verifying delivery signatures with the secret returned at creation time. After resuming, check [List Deliveries](/api-reference/webhooks/deliveries) for recent `status`, `attempts`, `lastStatusCode`, and `lastError` rows. Use delivery `streamEventId` with [Get Event](/api-reference/events/get) or event list pages to backfill missed downstream work. This endpoint supports **dual authentication**: API key (`x-api-key` header) or session cookie from the dashboard. **Related:** [List Webhooks](/api-reference/webhooks/list) · [Update Webhook](/api-reference/webhooks/update) · [Test Webhook](/api-reference/webhooks/test) · [List Deliveries](/api-reference/webhooks/deliveries) · [Webhook Verification](/webhooks/verification) # Twitter Webhook Test & Signed Delivery Check Source: https://docs.xquik.com/api-reference/webhooks/test POST /webhooks/{id}/test Send a signed test event to a webhook URL. Verify HTTPS reachability, response status, signature handling, and delivery timing. Includes API examples. ```json theme={null} { "success": true, "statusCode": 200 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/webhooks/15/test \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq --arg webhook_id "15" '{ webhook_id: $webhook_id, accepted: (.success == true), status_code: .statusCode, error: (.error // null) }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/webhooks/15/test", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const result = await response.json(); const testOutcome = { webhook_id: "15", accepted: result.success === true, status_code: result.statusCode, error: result.error ?? null, }; ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/webhooks/15/test", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) result = response.json() test_outcome = { "webhook_id": "15", "accepted": result["success"] is True, "status_code": result["statusCode"], "error": result.get("error"), } ``` ```go Go theme={null} package main import ( "encoding/json" "log" "net/http" ) type TestOutcome struct { WebhookID string `json:"webhook_id"` Accepted bool `json:"accepted"` StatusCode int `json:"status_code"` Error *string `json:"error"` } func main() { req, err := http.NewRequest("POST", "https://xquik.com/api/v1/webhooks/15/test", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result struct { Error *string `json:"error"` StatusCode int `json:"statusCode"` Success bool `json:"success"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { log.Fatal(err) } outcome := TestOutcome{ WebhookID: "15", Accepted: result.Success, StatusCode: result.StatusCode, Error: result.Error, } _ = outcome } ``` These snippets shape a small deployment-check record. Store `accepted`, `status_code`, and `error` with the webhook ID instead of printing the full test response. ## Path parameters The webhook ID to test. ## Headers Your API key. This endpoint also accepts session cookie authentication. ## What happens Xquik sends a `webhook.test` event to your endpoint, HMAC-signed with the webhook's secret: ```json Payload delivered to your endpoint theme={null} { "eventType": "webhook.test", "data": { "message": "Test delivery from Xquik" }, "timestamp": "2026-02-27T12:00:00.000Z" } ``` The signed request includes the `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` headers. Verify the signature against the raw request body before parsing JSON, reject stale timestamps, and de-dupe recent nonces exactly as you do for production monitor deliveries. You can test active, paused, or needs-attention webhooks. This endpoint reports whether the receiver accepted the signed test request, but it does not change `isActive`, `deliveryStatus`, or `consecutiveFailures`. Use [Resume Webhook](/api-reference/webhooks/resume) when a fixed receiver should pass a signed test before delivery resumes. The test endpoint does not return or rotate the signing secret. Keep using the secret returned by [Create Webhook](/api-reference/webhooks/create) for signature verification, and keep raw request bodies, raw signatures, and full headers out of deployment logs. `webhook.test` payloads include `eventType`, `data`, and `timestamp`. They do not include `deliveryId` or `streamEventId`, so use them for reachability and signature checks rather than receiver idempotency checks. ## Response ### 200 OK (success) `true` when your endpoint responded with a 2xx status code. The HTTP status code returned by your endpoint. ```json theme={null} { "success": true, "statusCode": 200 } ``` ### 200 OK (delivery failed) `false` when your endpoint returned a non-2xx status or was unreachable. The HTTP status code returned by your endpoint, or `0` if unreachable. Error description (e.g. `"HTTP 500"` or a network error message). ```json theme={null} { "success": false, "statusCode": 500, "error": "HTTP 500" } ``` ### 400 Invalid Request ```json theme={null} { "error": "invalid_id", "message": "Invalid webhook ID format" } ``` The provided webhook ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key / session cookie. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Webhook not found" } ``` No webhook exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Test result handoff Use this endpoint before routing production monitor events to a new receiver or after changing webhook code, secrets, firewall rules, or queue routing. Treat `success: true` and a `2xx` `statusCode` as proof that the receiver accepted the signed `webhook.test` request. Treat `success: false` with a non-`2xx` `statusCode` as a receiver error. Fix the endpoint before waiting for the next production monitor event. Treat `statusCode: 0` as a network or reachability failure. Check DNS, TLS, firewall rules, and the public HTTPS URL. Store `error` with your deployment logs so support, queue, or incident workers can see the latest test failure reason. Tests are still sent to paused and needs-attention webhooks. A passing test proves reachability only; use [Resume Webhook](/api-reference/webhooks/resume) when delivery should resume after the test. Validate `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` on the raw request body before accepting test or production events. `webhook.test` payloads include `eventType`, `data`, and `timestamp`. They do not include `deliveryId` or `streamEventId`. After the receiver accepts this signed test, use [List Deliveries](/api-reference/webhooks/deliveries) to debug real monitor events. Delivery rows contain `id`, `streamEventId`, `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`. For failed or exhausted production deliveries, use delivery `streamEventId` as the `{id}` for [Get Event](/api-reference/events/get). Store the event `monitorId`, `monitorType`, `type`, `occurredAt`, and `data` with the receiver incident. This endpoint supports **dual authentication**: API key (`x-api-key` header) or session cookie from the dashboard. **Related:** [List Webhooks](/api-reference/webhooks/list) · [Resume Webhook](/api-reference/webhooks/resume) · [List Deliveries](/api-reference/webhooks/deliveries) · [Get Event](/api-reference/events/get) · [Webhook Verification](/webhooks/verification) # Update Twitter Webhook Endpoint & Event Filters Source: https://docs.xquik.com/api-reference/webhooks/update PATCH /webhooks/{id} Change a webhook's HTTPS URL, tweet, follower, profile, relationship, or keyword event filters, and active delivery state. Includes exact API examples. ```json theme={null} { "id": "42", "url": "https://example.com/webhook", "eventTypes": [ "tweet.new" ], "isActive": true, "consecutiveFailures": 0, "deliveryStatus": "active", "failureHardCap": 200, "createdAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits ```bash cURL theme={null} curl -X PATCH https://xquik.com/api/v1/webhooks/15 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://new-server.com/webhook", "eventTypes": ["tweet.new"], "isActive": false }' | jq '{ webhook_id: .id, url, event_types: .eventTypes, is_active: .isActive, delivery_status: .deliveryStatus, consecutive_failures: .consecutiveFailures, failure_hard_cap: .failureHardCap, test_endpoint: ("/api/v1/webhooks/" + .id + "/test"), resume_endpoint: ("/api/v1/webhooks/" + .id + "/resume"), deliveries_endpoint: ("/api/v1/webhooks/" + .id + "/deliveries") }' ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/webhooks/15", { method: "PATCH", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://new-server.com/webhook", eventTypes: ["tweet.new"], isActive: false, }), }); const webhook = await response.json(); const updateHandoff = { webhook_id: webhook.id, url: webhook.url, event_types: webhook.eventTypes, is_active: webhook.isActive, delivery_status: webhook.deliveryStatus, consecutive_failures: webhook.consecutiveFailures, failure_hard_cap: webhook.failureHardCap, test_endpoint: `/api/v1/webhooks/${webhook.id}/test`, resume_endpoint: `/api/v1/webhooks/${webhook.id}/resume`, deliveries_endpoint: `/api/v1/webhooks/${webhook.id}/deliveries`, }; ``` ```python Python theme={null} import requests response = requests.patch( "https://xquik.com/api/v1/webhooks/15", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "url": "https://new-server.com/webhook", "eventTypes": ["tweet.new"], "isActive": False, }, ) webhook = response.json() update_handoff = { "webhook_id": webhook["id"], "url": webhook["url"], "event_types": webhook["eventTypes"], "is_active": webhook["isActive"], "delivery_status": webhook["deliveryStatus"], "consecutive_failures": webhook["consecutiveFailures"], "failure_hard_cap": webhook["failureHardCap"], "test_endpoint": f"/api/v1/webhooks/{webhook['id']}/test", "resume_endpoint": f"/api/v1/webhooks/{webhook['id']}/resume", "deliveries_endpoint": f"/api/v1/webhooks/{webhook['id']}/deliveries", } ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "log" "net/http" ) type Webhook struct { ConsecutiveFailures int `json:"consecutiveFailures"` DeliveryStatus string `json:"deliveryStatus"` EventTypes []string `json:"eventTypes"` FailureHardCap int `json:"failureHardCap"` ID string `json:"id"` IsActive bool `json:"isActive"` URL string `json:"url"` } type UpdateHandoff struct { ConsecutiveFailures int `json:"consecutive_failures"` DeliveriesEndpoint string `json:"deliveries_endpoint"` DeliveryStatus string `json:"delivery_status"` EventTypes []string `json:"event_types"` FailureHardCap int `json:"failure_hard_cap"` IsActive bool `json:"is_active"` ResumeEndpoint string `json:"resume_endpoint"` TestEndpoint string `json:"test_endpoint"` URL string `json:"url"` WebhookID string `json:"webhook_id"` } func main() { payload := map[string]interface{}{ "url": "https://new-server.com/webhook", "eventTypes": []string{"tweet.new"}, "isActive": false, } body, err := json.Marshal(payload) if err != nil { log.Fatal(err) } req, err := http.NewRequest("PATCH", "https://xquik.com/api/v1/webhooks/15", bytes.NewReader(body)) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var webhook Webhook if err := json.NewDecoder(resp.Body).Decode(&webhook); err != nil { log.Fatal(err) } handoff := UpdateHandoff{ ConsecutiveFailures: webhook.ConsecutiveFailures, DeliveriesEndpoint: "/api/v1/webhooks/" + webhook.ID + "/deliveries", DeliveryStatus: webhook.DeliveryStatus, EventTypes: webhook.EventTypes, FailureHardCap: webhook.FailureHardCap, IsActive: webhook.IsActive, ResumeEndpoint: "/api/v1/webhooks/" + webhook.ID + "/resume", TestEndpoint: "/api/v1/webhooks/" + webhook.ID + "/test", URL: webhook.URL, WebhookID: webhook.ID, } _ = handoff } ``` These snippets shape a reconfiguration row. Store the current webhook configuration with its test and delivery-log endpoints instead of printing the full update response. | Webhook update column | Request or response source | Verification rule | | --------------------- | --------------------------------- | --------------------------------------------------- | | Webhook ID | Path `{id}` and response `id` | Require both IDs to match. | | Receiver URL | Request and response `url` | Confirm the intended HTTPS endpoint. | | Event filter | Request and response `eventTypes` | Confirm the complete replacement list. | | Active state | Request and response `isActive` | Verify the intended pause or activation. | | Failure count | Response `consecutiveFailures` | Confirm whether activation reset failures. | | Delivery state | Response `deliveryStatus` | Route active, paused, or needs-attention receivers. | | Failure cap | Response `failureHardCap` | Preserve the receiver recovery threshold. | ## Path parameters The webhook ID to update. ## Headers Your API key. This endpoint also accepts session cookie authentication. Must be `application/json`. ## Body At least 1 field is required. New HTTPS endpoint URL. HTTP URLs are rejected. Updated event types to subscribe to. Replaces the existing list. At least 1 required when provided. Use any valid account monitor event type listed below. Keyword monitor webhooks should stay on `tweet.*` event types. Account monitor webhooks can use both `tweet.*` and `profile.*` event types. ## Valid event types Valid types: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, `tweet.longform`, `profile.avatar.changed`, `profile.banner.changed`, `profile.name.changed`, `profile.username.changed`, `profile.bio.changed`, `profile.location.changed`, `profile.url.changed`, `profile.verified.changed`, `profile.protected.changed`, `profile.pinned_tweet.changed`, `profile.unavailable.changed`. Original tweet from an account monitor or matching keyword monitor. Use for new posts that are not replies, quotes, or retweets. Quote tweet from an account monitor or matching keyword monitor. Keep this only when downstream systems handle quote payloads. Reply from an account monitor or matching keyword monitor. Keep this when reply alerts or support routing should continue. Retweet from an account monitor or matching keyword monitor. Keep this when repost activity should keep triggering deliveries. `webhook.test` is generated only by the [Test Webhook](/api-reference/webhooks/test) endpoint. It cannot be added to a webhook subscription. Set to `false` to pause deliveries. Set to `true` to reactivate a paused webhook and reset `consecutiveFailures`. When `deliveryStatus` is `needs_attention`, use [Resume Webhook](/api-reference/webhooks/resume) so the receiver must pass a signed test first. ## Response ### 200 OK Unique webhook identifier. The current delivery endpoint URL. Event types this webhook is subscribed to. Whether the webhook is currently active. Consecutive delivery failures recorded for this webhook. `active`, `paused`, or `needs_attention`. Failure count where the webhook needs attention before resuming. ISO 8601 creation timestamp. ```json theme={null} { "id": "15", "url": "https://new-server.com/webhook", "eventTypes": ["tweet.new"], "isActive": false, "consecutiveFailures": 0, "deliveryStatus": "paused", "failureHardCap": 200, "createdAt": "2026-02-24T10:30:00.000Z" } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key / session cookie. ### 400 Invalid Input ```json theme={null} { "error": "invalid_input" } ``` Invalid URL (must be HTTPS), empty `eventTypes`, or no fields provided. ```json theme={null} { "error": "invalid_id" } ``` The provided webhook ID is not a valid format. ### 404 Not Found ```json theme={null} { "error": "not_found" } ``` No webhook exists with this ID, or it belongs to a different account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Too many requests. Wait for the `Retry-After` header before retrying. ## Reconfiguration handoff Use this endpoint when a receiver URL changes, an event filter changes, or an operator needs to pause or resume webhook delivery without creating a new endpoint. Store returned `id`, `url`, `eventTypes`, `isActive`, `deliveryStatus`, `consecutiveFailures`, `failureHardCap`, and `createdAt` as the current webhook configuration. After changing `url`, run [Test Webhook](/api-reference/webhooks/test) before expecting production monitor events at the new receiver. `eventTypes` replaces the previous list. Keep it aligned with account or keyword monitor event types. `isActive: false` stops future deliveries. Existing stored events and delivery records remain available. `isActive: true` resumes delivery for matching future monitor events. Test the receiver after resuming. Use [Resume Webhook](/api-reference/webhooks/resume) when the receiver needs a signed test gate first. This endpoint does not rotate or return `secret`. Keep using the secret from [Create Webhook](/api-reference/webhooks/create) for signature verification. After the signed test passes, use [List Deliveries](/api-reference/webhooks/deliveries) if production monitor events still miss the receiver. Check `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`. Use delivery `streamEventId` as the `{id}` for [Get Event](/api-reference/events/get). Store `monitorId`, `monitorType`, `type`, `occurredAt`, and `data` with the reconfiguration incident. This endpoint supports **dual authentication**: API key (`x-api-key` header) or session cookie from the dashboard. **Related:** [List Webhooks](/api-reference/webhooks/list) · [Test Webhook](/api-reference/webhooks/test) · [Resume Webhook](/api-reference/webhooks/resume) · [List Deliveries](/api-reference/webhooks/deliveries) · [Get Event](/api-reference/events/get) · [Delete Webhook](/api-reference/webhooks/delete) # X Account Connection API | Retry Failed Accounts Source: https://docs.xquik.com/api-reference/x-accounts/bulk-retry POST /x/accounts/bulk-retry Clear only temporary login failures; use re-authentication or X-side fixes for credentials, TOTP, passkeys, locked, or suspended accounts. See fields. ```json theme={null} { "cleared": 3 } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Bulk retry only clears `transient` and `automated` login-failure states. It does not update passwords, TOTP secret keys, passkeys, email challenges, locked accounts, or suspended accounts. Use re-authentication or reconnect for credential and 2FA fixes, and resolve locks or suspensions on X first. Clears temporary login-failure state for accounts that can safely retry on the next read, write, monitor, or account action. Use it when the dashboard shows accounts with temporary issues. ## What gets retried Xquik clears accounts whose stored failure reason is `transient` or `automated`. These accounts become eligible to reconnect on their next use. Accounts that need fresh credentials or a security challenge stay unchanged. Use [Re-authenticate](/api-reference/x-accounts/reauth) or reconnect the account instead. Locked and suspended accounts stay unchanged. Resolve the restriction on X first, then re-authenticate or reconnect if needed. The API returns only `cleared`, the number of accounts reset for retry. Call [List X Accounts](/api-reference/x-accounts/list) before and after if you need per-account status. The dashboard button follows the same model. It appears when accounts have temporary issues, asks for confirmation, clears retryable failures, then refreshes the account list. It does not reconnect the accounts immediately. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/accounts/bulk-retry \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/accounts/bulk-retry", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); console.log(`Cleared ${data.cleared} accounts`); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/x/accounts/bulk-retry", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() print(f"Cleared {data['cleared']} accounts") ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/accounts/bulk-retry", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Printf("Cleared %.0f accounts\n", data["cleared"]) } ``` ## Headers Your API key. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Number of accounts cleared for retry. ```json theme={null} { "cleared": 3 } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before retrying account recovery again. **Related:** [List X Accounts](/api-reference/x-accounts/list) to check account statuses, or [Re-authenticate](/api-reference/x-accounts/reauth) to fix a specific account manually. # Connect X Account API & Profile Login Workflow Source: https://docs.xquik.com/api-reference/x-accounts/connect POST /x/accounts Connect an X account with username, password, and its saved Authenticator App TOTP secret for durable tweet, reply, DM, and profile actions. See costs. ```json theme={null} { "id": "42", "xUserId": "9876543210", "xUsername": "elonmusk", "status": "active", "health": "healthy", "createdAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "object": "x_account_connection_attempt", "id": "xatt_0123456789abcdef0123456789abcdef", "status": "pending", "pollAfterMs": 3000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "account_already_connected", "message": "This X account is already connected." } ``` ```json theme={null} { "error": "login_failed", "message": "Login failed. Check credentials and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Retry later." } ``` ```json theme={null} { "error": "x_user_lookup_failed", "message": "X username lookup failed. Try again later." } ``` ```json theme={null} { "error": "service_unavailable", "message": "Service temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Xquik encrypts credentials at rest. It only uses them to maintain the connection. Xquik never stores plaintext passwords. Authenticator App 2FA and `totp_secret` are required for a durable Xquik connection. Missing the key? Restart Authentication App 2FA in X to reveal a new secret. Copy it, add it to your authenticator app, and finish X's 6-digit confirmation. Then send the saved long key as `totp_secret`. Custom, dedicated, and user-supplied proxies are not supported. Xquik does not guarantee one fixed public IP per connected account. A connection can finish immediately, continue as a tracked attempt, or ask for an email code. If it continues, follow the returned `Location`. Do not send the credentials again while the attempt is `pending`. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/accounts \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "username": "your_x_username", "email": "account@example.invalid", "password": "", "totp_secret": "" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/accounts", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ username: "your_x_username", email: "account@example.invalid", password: "", totp_secret: "", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/x/accounts", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "username": "your_x_username", "email": "account@example.invalid", "password": "", "totp_secret": "", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "username": "your_x_username", "email": "account@example.invalid", "password": "", "totp_secret": "", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/accounts", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` | X account connection result | Response source | Next step | | --------------------------- | ----------------------------------- | ------------------------------------------------- | | Connected account ID | `id` from `201` | Use this ID for write actions and recovery. | | X username | `xUsername` | Confirm the intended profile connected. | | X user ID | `xUserId` | Preserve the stable account identity. | | Connection state | `status` | Continue only after the account becomes active. | | Login health | `health` | Require `healthy` before durable actions. | | Pending attempt | `id` from `202 pending` | Poll the returned `Location` after `Retry-After`. | | Email challenge | `id` from `202 requires_email_code` | Submit the matching email code before expiry. | | Challenge expiry | `expiresAt` | Stop using an expired challenge. | ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body X username to connect. The `@` prefix is automatically stripped if included. Email address associated with the X account. Password for the X account. Encrypted at rest immediately upon receipt. Authenticator App TOTP secret required for a durable connection. This is the base32-encoded secret, not the 6-digit code. ## 2FA secret key setup Xquik needs the authenticator app secret key, not a live 6-digit code. The key is the long base32 string X shows while you set up **Authentication App** 2FA, for example `JBSWY3DPEHPK3PXP`. Paste the long base32 key into `totp_secret`. Xquik uses it to generate fresh 2FA codes during login challenges. Do not paste the 6-digit authenticator code, the 12-character backup code, a passkey, or a security key prompt. If you already saved the secret key, send it as `totp_secret`. If you did not save it, create a fresh authenticator app secret on X: Paste that saved base32 secret into `totp_secret`. Do not paste the current 6-digit authenticator code. X shows the text secret only during Authentication App setup. Turn Authentication App off, turn it on again, copy the new key, then finish setup on X. Start Authentication App setup on X, reveal the text secret, copy it, add it to your authenticator app, confirm the 6-digit code on X, then connect. 1. Open X **Settings and Privacy > Security and Account Access > Security**. 2. Open **Two-Step Verification > Authentication App**. 3. Turn Authentication App off. 4. Turn Authentication App on again. 5. When the QR code appears, choose **Can't scan the QR code?** to reveal the text secret. 6. Copy the long secret key and store it safely before leaving the setup screen. 7. Add that key to your authenticator app if you are setting it up fresh. 8. Finish enabling 2FA on X by entering the current 6-digit code from your authenticator app. 9. Send the saved long key in `totp_secret` when you call Xquik. Do not stop after copying the secret key. Complete the X-side 2FA confirmation before starting the Xquik connection, or the key will not work. Passkeys and security keys cannot satisfy this flow. Use Authenticator App 2FA. ## Response ### 201 Created Unique account ID. Connected X username. X user ID. Account connection status (e.g. `"active"`). Derived login/cookie health. One of `healthy`, `locked`, `needsReauth`, `recovering`, `suspended`, `temporaryIssue`. See [Account health](/api-reference/x-accounts/list#account-health) for meanings. ISO 8601 timestamp of when the account was connected. ```json theme={null} { "id": "3", "xUsername": "your_x_username", "xUserId": "9876543210", "status": "active", "health": "healthy", "createdAt": "2026-02-20T08:15:00.000Z" } ``` ### 202 Connecting Always `x_account_connection_attempt`. Connection attempt ID. Always `pending`. Milliseconds to wait before checking the status URL. ```json theme={null} { "object": "x_account_connection_attempt", "id": "xatt_0123456789abcdef0123456789abcdef", "status": "pending", "pollAfterMs": 3000 } ``` The response includes: * `Location: /api/v1/x/account-connection-attempts/{id}` * `Retry-After: 3` * `Cache-Control: no-store` Wait for `Retry-After`, then call [Get X Account Connection Status](/api-reference/x-accounts/connection-attempt). Keep checking while `status` is `pending`. Do not create another attempt. ### 202 Email Code Required Always `x_account_connection_challenge`. Challenge ID to submit with the email verification code. Always `requires_email_code`. ISO 8601 expiration time for the challenge. Human-readable next step. X username being connected. ```json theme={null} { "object": "x_account_connection_challenge", "id": "xch_8vGd8Y9JvH6dV0xA", "status": "requires_email_code", "expiresAt": "2026-05-08T12:10:00Z", "message": "Enter the email verification code to continue.", "username": "elonmusk" } ``` Submit the code to [Submit X Account Email Code](/api-reference/x-accounts/submit-challenge). ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` Missing `username`, `email`, `password`, or `totp_secret`, or invalid field format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 409 Duplicate ```json theme={null} { "error": "account_already_connected", "message": "This X account is already connected." } ``` The specified X account is already connected to your Xquik account. ### 429 Rate Limit Exceeded ```json theme={null} { "error": "rate_limit_exceeded", "message": "Connection safety limit reached. Try again later.", "retryAfter": 900 } ``` The connection safety limit was reached. The response includes a `Retry-After: 900` header indicating how many seconds to wait before retrying. See the [rate limits guide](/guides/rate-limits) for details. ### 429 Login Cooldown ```json theme={null} { "error": "login_cooldown", "message": "Login is temporarily paused", "reason": "automated", "retryAfterMs": 3600000 } ``` A prior login attempt triggered a cooldown (for example, X flagged the session). Wait for `retryAfterMs` before retrying. The response includes a `Retry-After` header in seconds. ### 422 Login Failed ```json theme={null} { "error": "login_failed", "message": "Login failed. Check credentials and try again.", "retryAfterMs": 300000 } ``` X rejected the submitted username, email, password, or TOTP secret. Retry with the current password and the saved Authenticator App secret key, not a 6-digit code. When `retryAfterMs` is present, wait for that exact duration. The response also includes `Retry-After` in seconds. ```json theme={null} { "error": "passkey_required", "message": "Passkey verification is not supported. Use Authenticator app 2FA for this X account, then try again." } ``` X asked for passkey verification. Switch the account to Authenticator App 2FA, save the long TOTP secret key, finish setup on X, then connect with `totp_secret`. ### 502 X User Lookup Failed ```json theme={null} { "error": "x_user_lookup_failed", "message": "X user not found. Check the username." } ``` The X username could not be resolved. Verify the handle is correct and that the account exists. ### 503 Service Unavailable ```json theme={null} { "error": "service_unavailable", "message": "Service temporarily unavailable. Try again." } ``` The X connection service is temporarily unavailable. Retry after a short delay. **Related:** [Get X Account Connection Status](/api-reference/x-accounts/connection-attempt) for a pending attempt, [List X Accounts](/api-reference/x-accounts/list) to see connected accounts, or [Re-authenticate](/api-reference/x-accounts/reauth) if a connection expires later. # X Account Connection Status API & Login Challenges Source: https://docs.xquik.com/api-reference/x-accounts/connection-attempt GET /x/account-connection-attempts/{id} Poll a tracked X account connection until it succeeds, fails, needs reauthentication, or requests an email verification code. Includes request fields. ```json theme={null} { "object": "x_account_connection_attempt", "id": "xatt_0123456789abcdef0123456789abcdef", "status": "pending", "pollAfterMs": 3000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Check this endpoint after the connect response's `Retry-After` delay. Keep using Xquik while the connection continues. Do not send the credentials again while `status` is `pending`. ```bash cURL theme={null} curl https://xquik.com/api/v1/x/account-connection-attempts/xatt_0123456789abcdef0123456789abcdef \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const attemptId = "xatt_0123456789abcdef0123456789abcdef"; const response = await fetch( `https://xquik.com/api/v1/x/account-connection-attempts/${attemptId}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }, ); const data = await response.json(); ``` ```python Python theme={null} import requests attempt_id = "xatt_0123456789abcdef0123456789abcdef" response = requests.get( f"https://xquik.com/api/v1/x/account-connection-attempts/{attempt_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { attemptID := "xatt_0123456789abcdef0123456789abcdef" url := "https://xquik.com/api/v1/x/account-connection-attempts/" + attemptID req, err := http.NewRequest("GET", url, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Path Connection attempt ID from the `202 pending` connect response. ## Response Every `200` response includes `Cache-Control: no-store`. ### 200 Pending Always `x_account_connection_attempt`. Connection attempt ID. Always `pending`. Milliseconds to wait before checking again. ```json theme={null} { "object": "x_account_connection_attempt", "id": "xatt_0123456789abcdef0123456789abcdef", "status": "pending", "pollAfterMs": 3000 } ``` The response includes `Retry-After: 3`. Wait, then check the same attempt. ### 200 Success Always `x_account_connection_attempt`. Connection attempt ID. Always `success`. ```json theme={null} { "object": "x_account_connection_attempt", "id": "xatt_0123456789abcdef0123456789abcdef", "status": "success" } ``` The account is ready. Call [List X Accounts](/api-reference/x-accounts/list) if you need its account ID. ### 200 Email Code Required Always `x_account_connection_challenge`. Challenge ID, not the attempt ID. Always `requires_email_code`. ISO 8601 challenge expiration time. Human-readable next step. X username being connected. ```json theme={null} { "object": "x_account_connection_challenge", "id": "xch_8vGd8Y9JvH6dV0xA", "status": "requires_email_code", "expiresAt": "2026-05-08T12:10:00Z", "message": "Enter the email verification code to continue.", "username": "your_x_username" } ``` Submit the newest code to [Submit X Account Email Code](/api-reference/x-accounts/submit-challenge). ### 200 Failed Always `x_account_connection_attempt`. Connection attempt ID. Always `failed`. Stable public error code. More specific reason when available. Whether another connect request can be attempted. ```json theme={null} { "object": "x_account_connection_attempt", "id": "xatt_0123456789abcdef0123456789abcdef", "status": "failed", "error": "service_unavailable", "retryable": true } ``` Stop checking this attempt. Start a new connection only when appropriate. ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid connection attempt ID. Check the path parameter." } ``` The attempt ID is malformed, unavailable, or belongs to another account. Do not rely on indefinite attempt retention. Save the terminal result. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` The API key is missing or invalid. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Wait for `Retry-After` before checking again. **Related:** [Connect X Account](/api-reference/x-accounts/connect) starts the attempt. [Submit X Account Email Code](/api-reference/x-accounts/submit-challenge) completes an email challenge. # X Account Connection API | Disconnect Profile Source: https://docs.xquik.com/api-reference/x-accounts/disconnect DELETE /x/accounts/{id} Delete the stored Xquik connection only; the X account stays unchanged, old IDs return 404, and reconnecting creates a new ID. Includes request fields. ```json theme={null} { "success": true } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Use this endpoint when a connected account should no longer be available for write actions, DMs, media upload, or profile updates. It deletes only the stored Xquik connection for that account ID. It does not change the X account itself. After success, the old Xquik account ID returns `404`; reconnect the account to get a new ID. ## What disconnect does The stored connection row is removed from your Xquik account. Future `GET /x/accounts/{id}` calls for the same ID return `404`. The dashboard Disconnect button uses the same endpoint and warns that write actions stop immediately. After success, new write, DM, media upload, and profile actions must choose another connected account or reconnect this X account. Account and keyword monitors are independent. Disconnecting credentials for `@username` does not remove monitors that track that username. Delete those monitors separately when tracking should stop. To use the same X account again, call [Connect X Account](/api-reference/x-accounts/connect). Store the new account `id` from the connect response instead of reusing the deleted ID. ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/x/accounts/3 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const accountId = "3"; const response = await fetch(`https://xquik.com/api/v1/x/accounts/${accountId}`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); ``` ```python Python theme={null} import requests account_id = "3" response = requests.delete( f"https://xquik.com/api/v1/x/accounts/{account_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { accountID := "3" req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/x/accounts/"+accountID, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Path parameters The unique account ID. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Always `true` on successful disconnection. ```json theme={null} { "success": true } ``` ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid account ID format" } ``` The provided account ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` No account exists with this ID, or it belongs to a different Xquik account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before disconnecting another account. The account is disconnected and stored credentials are permanently deleted. This does not affect your X account itself. **Related:** [List X Accounts](/api-reference/x-accounts/list) to verify the account was removed, or [Connect X Account](/api-reference/x-accounts/connect) to add a new one. # X Account Connection API | Get Profile Status Source: https://docs.xquik.com/api-reference/x-accounts/get GET /x/accounts/{id} Check a connected X account before tweets, replies, DMs, likes, follows, or profile updates. Read healthy, recovering, temporaryIssue, or needsReauth. ```json theme={null} { "id": "42", "xUserId": "9876543210", "xUsername": "elonmusk", "status": "active", "health": "healthy", "createdAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
## Choose One Account Health Check Use this route as a health gate before one connected-account write. Read health, recovery, and credential state before selecting the next action. Use list only for account-wide inventory. **Free** - does not consume credits Use this endpoint before a workflow acts with one connected account. Read `health` first. Write with `healthy`. Use `recovering` on the next action. Wait or bulk retry `temporaryIssue`. Re-authenticate `needsReauth`. Fix `locked` or `suspended` on X before retrying writes. ## Read the account state `health: "healthy"` means the stored session is usable. `cookiesObtainedAt` shows when the session was last obtained and is omitted if the account has not authenticated yet. `health: "needsReauth"` means credentials, TOTP, email verification, passkey, or another security challenge blocked login. Use [Re-authenticate](/api-reference/x-accounts/reauth) with current credentials and a valid TOTP secret before retrying writes. `health: "temporaryIssue"` means a transient or automated cooldown is still active. Wait for recovery, or use [Bulk retry](/api-reference/x-accounts/bulk-retry) for temporary failures. `health: "recovering"` means the account can reconnect on its next use. `health: "locked"` or `health: "suspended"` means writes stay blocked until the account is fixed on X. Re-authenticate or reconnect only after the account is usable again. ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/x/accounts/3 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const accountId = "3"; const response = await fetch(`https://xquik.com/api/v1/x/accounts/${accountId}`, { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); ``` ```python Python theme={null} import requests account_id = "3" response = requests.get( f"https://xquik.com/api/v1/x/accounts/{account_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { accountID := "3" req, err := http.NewRequest("GET", "https://xquik.com/api/v1/x/accounts/"+accountID, nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Path parameters The unique account ID. Returned when you [connect an account](/api-reference/x-accounts/connect) or [list accounts](/api-reference/x-accounts/list). ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). ## Response ### 200 OK Unique account ID. X username. X user ID. Account connection status (e.g. `"active"`). Derived login/cookie health. One of `healthy`, `locked`, `needsReauth`, `recovering`, `suspended`, `temporaryIssue`. See [Account health](/api-reference/x-accounts/list#account-health) for meanings. ISO 8601 timestamp of when session cookies were last obtained. Omitted if not yet authenticated. ISO 8601 timestamp of when the account was connected. ISO 8601 timestamp of the last update. ```json theme={null} { "id": "3", "xUsername": "elonmusk", "xUserId": "44196397", "status": "active", "health": "healthy", "cookiesObtainedAt": "2026-02-20T08:15:00.000Z", "createdAt": "2026-02-20T08:15:00.000Z", "updatedAt": "2026-02-20T08:15:00.000Z" } ``` ### 400 Invalid ID ```json theme={null} { "error": "invalid_id", "message": "Invalid account ID format" } ``` The provided account ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` No account exists with this ID, or it belongs to a different Xquik account. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` Wait for the `Retry-After` value before fetching this account again. **Related:** [List X Accounts](/api-reference/x-accounts/list) to see all accounts, [Disconnect](/api-reference/x-accounts/disconnect) to remove this account, or [Re-authenticate](/api-reference/x-accounts/reauth) if the session has expired. # Connected X Accounts API, Health & Write Readiness Source: https://docs.xquik.com/api-reference/x-accounts/list GET /x/accounts List every connected X account, read its Xquik ID and username, inspect login health, and choose whether to write, reauthenticate, wait, or retry safely. ```json theme={null} { "accounts": [ { "id": "42", "xUserId": "9876543210", "xUsername": "elonmusk", "status": "active", "health": "healthy" } ] } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
**Free.** This endpoint does not consume credits. List every X account connected to the authenticated Xquik account. Read each local connection ID, X username, X user ID, and login health before writes. This endpoint lists your Xquik connections. It does not search public Twitter accounts. Use [Search Users](/api-reference/x/search-users) for public profile search. Check `accounts[].health` before scheduling writes. `healthy` can write now. `recovering` can reconnect on the next account use. `temporaryIssue` is still paused by a transient cooldown. `needsReauth` requires credentials, TOTP, or a completed security challenge. `locked` and `suspended` stay blocked until the account is fixed on X. ## When to List Connected X Accounts | Workflow | Use the response for | | ---------------------------------------------------------- | ----------------------------------------------------------- | | Before a Tweet, reply, DM, like, follow, or profile update | Select `accounts[].id` and require acceptable health | | After connecting an account | Confirm the new username and local connection ID | | After reauthentication | Confirm that `health` changed before resuming writes | | Before scheduled automation | Exclude blocked accounts and defer temporary cooldowns | | During account support | Compare username, X user ID, timestamps, status, and health | ## Scope, Ordering & Pagination The response contains only connections owned by the authenticated Xquik account. It returns every matching connection in one array. The endpoint accepts no query parameters. It does not paginate, search, or filter. Accounts are ordered by `createdAt` from earliest to latest. An Xquik account without connected X profiles receives `{"accounts": []}`. Connect one account before calling write endpoints. ```bash cURL theme={null} curl -X GET https://xquik.com/api/v1/x/accounts \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/accounts", { method: "GET", headers: { "x-api-key": "xq_YOUR_KEY_HERE", }, }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/x/accounts", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://xquik.com/api/v1/x/accounts", nil) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Headers Send an Xquik API key or OAuth 2.1 bearer token. Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). OAuth bearer token using `Bearer YOUR_TOKEN`. ## Response ### 200 OK Account-scoped connected X profiles. Empty when no connection exists. Xquik connection ID. Use it as `accountId` for write endpoints. Connected X username without a leading `@`. Stable X user ID as a string. This is not the Xquik connection ID. Stored connection status. The documented response example uses `active`. Derived login/cookie health. One of `healthy`, `locked`, `needsReauth`, `recovering`, `suspended`, or `temporaryIssue`. See [Account Health](#account-health) below. ISO 8601 time when the stored X session was obtained. Omitted before login. ISO 8601 time when this Xquik connection was created. ISO 8601 time when this connection row last changed. ```json theme={null} { "accounts": [ { "id": "3", "xUsername": "elonmusk", "xUserId": "44196397", "status": "active", "health": "healthy", "cookiesObtainedAt": "2026-02-20T08:15:00.000Z", "createdAt": "2026-02-20T08:15:00.000Z", "updatedAt": "2026-02-20T08:15:00.000Z" }, { "id": "5", "xUsername": "xquik_", "xUserId": "1234567890", "status": "active", "health": "healthy", "cookiesObtainedAt": "2026-02-22T12:00:00.000Z", "createdAt": "2026-02-22T12:00:00.000Z", "updatedAt": "2026-02-22T12:00:00.000Z" } ] } ``` ## Choose the Correct Account Identifier | Field | Identifier scope | Use | | ----------- | ------------------ | -------------------------------------------------------------------------------- | | `id` | Xquik connection | Pass as `accountId` for Tweets, replies, DMs, likes, follows, and profile writes | | `xUserId` | X platform account | Compare X identities or correlate read responses | | `xUsername` | Public X handle | Display the account and confirm operator intent | Do not send `xUserId` where a write endpoint requires `accountId`. Reconnecting after disconnection creates a new Xquik connection ID. ## Preflight a Write 1. List connected X accounts. 2. Match the intended `xUsername` or `xUserId`. 3. Read `health` before selecting the local `id`. 4. Continue immediately only when the account is ready. 5. Reauthenticate, wait, or retry according to the health table. The `status` field alone is insufficient. An `active` connection can still need reauthentication or an X-side recovery. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` Send a valid Xquik API key or OAuth bearer token. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before listing accounts again. **Related:** [Connect X Account](/api-reference/x-accounts/connect) to add a new account, or [Get X Account](/api-reference/x-accounts/get) to fetch details for a specific account. ## Account Health The `health` field is derived from recent connection state. Read it before writes. Your workflow can proceed, wait, or request an operator action. Cookies are valid. Writes can proceed. Credentials, TOTP, email verification, passkey, or another security challenge blocked login. Use [reauth](/api-reference/x-accounts/reauth) with current credentials and a valid TOTP secret before retrying writes. X locked the account or requires account-side verification. Complete the X check, then reauth or reconnect after the account works again. X suspended the account. Appeal on X. Writes stay paused until the account is restored. The transient cooldown ended. The account can reconnect on its next use. Transient or automated cooldown is still active. Wait for recovery, or use [bulk retry](/api-reference/x-accounts/bulk-retry) for temporary failures. ## Account Health Decisions | Health | Write readiness | Next action | | ---------------- | ----------------- | --------------------------------------------------------------- | | `healthy` | Ready | Use `id` with the intended write endpoint | | `recovering` | Retry on next use | Let the next account action reconnect | | `temporaryIssue` | Wait | Respect the cooldown or clear eligible failures with Bulk Retry | | `needsReauth` | Blocked | Send current credentials and the saved TOTP secret | | `locked` | Blocked | Complete the required recovery on X, then reauthenticate | | `suspended` | Blocked | Resolve the suspension on X before another Xquik write | Health is derived from recent connection state. It can change after a failed login, cooldown expiry, reauthentication, or X-side recovery. ## Connected X Account Questions ### Does This Endpoint Search Twitter Accounts? No. It lists only X accounts connected to your Xquik account. Use [Search Users](/api-reference/x/search-users) to find public profiles. Use [Profile Lookup](/api-reference/x/twitter-profile-lookup) for one username. ### Can I List Multiple Twitter Accounts? Yes. The `accounts` array contains every connection owned by the authenticated Xquik account. The endpoint returns them in connection order. It has no page cursor, search term, status filter, or health filter. Filter the returned array locally when your workflow manages several accounts. Always keep the selected `id` paired with its `xUsername`. ### Why Is an Active Account Not Ready to Write? `status` stores the connection state. `health` interprets recent login results. An active row can still be locked, suspended, cooling down, or awaiting new credentials. Gate writes on `health`, not `status` alone. ### What Is the Difference Between ID and X User ID? `id` identifies the Xquik connection. `xUserId` identifies the account on X. Write endpoints use the Xquik connection ID as `accountId`. Public read responses can use an X user ID for profile or Tweet relationships. ### Why Is cookiesObtainedAt Missing? The field is optional. It is omitted when the connection has not obtained a stored X session. Check `health` and complete the requested login action. ### Which Bearer Token Does This Endpoint Accept? Send an OAuth bearer token accepted by Xquik. Do not send an app-only token from the X developer platform. See [Authentication](/api-reference/authentication) for the Xquik authorization flow. ### When Should I Reauthenticate Instead of Bulk Retry? Use reauthentication for `needsReauth`. Supply current credentials and the saved Authenticator App TOTP secret. Use Bulk Retry only for eligible temporary failures. It cannot fix passwords, TOTP, passkeys, locks, or suspensions. ### How Do I Confirm Recovery? Call this endpoint after the operator finishes the required action. Confirm the expected username and health. Resume writes only when the returned state fits your retry policy. **Next steps:** [Connect X Account](/api-reference/x-accounts/connect), [Re-authenticate X Account](/api-reference/x-accounts/reauth), or [Bulk Retry](/api-reference/x-accounts/bulk-retry). # X Account Reauthentication API & Login Recovery Source: https://docs.xquik.com/api-reference/x-accounts/reauth POST /x/accounts/{id}/reauth Restore a connected X account by reusing its saved Authenticator App TOTP secret or sending a replacement for the login challenge. See request fields. ```json theme={null} { "id": "42", "xUserId": "9876543210", "xUsername": "elonmusk", "status": "active", "health": "healthy", "createdAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found." } ``` ```json theme={null} { "error": "login_failed", "message": "Login failed. Check credentials and try again." } ``` ```json theme={null} { "error": "login_cooldown", "message": "Login is temporarily paused" } ``` ```json theme={null} { "error": "service_unavailable", "message": "Service temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Use this when an account session expires or X requires re-verification. Omit `totp_secret` to reuse the saved key. Send a replacement only if X changed or rejected the saved key. Re-authentication does not guarantee the same public IP. Custom or fixed per-account proxy assignment is not supported. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/accounts/3/reauth \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "password": "" }' | jq ``` ```javascript Node.js theme={null} const accountId = "3"; const response = await fetch(`https://xquik.com/api/v1/x/accounts/${accountId}/reauth`, { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ password: "", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests account_id = "3" response = requests.post( f"https://xquik.com/api/v1/x/accounts/{account_id}/reauth", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "password": "", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "password": "", }) accountID := "3" req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/accounts/"+accountID+"/reauth", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` Send `totp_secret` only when the saved key no longer works. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/accounts/3/reauth \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "password": "", "totp_secret": "" }' | jq ``` | X account recovery column | Request or response source | Recovery rule | | ------------------------- | ----------------------------- | ---------------------------------------------- | | Account ID | Path `{id}` and response `id` | Require both IDs to match. | | Current password | Request `password` | Send it only to this reauthentication request. | | Saved TOTP key | Omitted `totp_secret` | Reuse the stored authenticator secret. | | Replacement TOTP key | Request `totp_secret` | Send the base32 key, never a 6-digit code. | | X username | Response `xUsername` | Confirm the recovered profile. | | X user ID | Response `xUserId` | Preserve the stable account identity. | | Account state | Response `status` | Require `active` before write actions. | | Login health | Response `health` | Require `healthy` before durable actions. | ## Path parameters The unique account ID. ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Body Current password for the X account. Replacement Authenticator App TOTP secret. Omit it to reuse the saved key. Send the base32 secret, not the 6-digit code. Email for the X account. Updates the stored email during re-authentication. ## 2FA re-authentication Xquik reuses the saved TOTP secret by default. Send `totp_secret` only when replacing that key. Never send a 6-digit code, backup code, passkey, or security key prompt. If you never saved the secret key, or X rejects the current one, reset the authenticator app setup before re-authenticating: Omit `totp_secret`. Xquik reuses the encrypted key saved during connection. X shows the text secret only during Authentication App setup. Turn Authentication App off, turn it on again, copy the new key, then finish setup on X. Treat the old TOTP secret as stale. Reset Authentication App setup on X, save the new long key, finish 2FA confirmation, then re-authenticate. 1. Open X **Settings and Privacy > Security and Account Access > Security**. 2. Open **Two-Step Verification > Authentication App**. 3. Turn Authentication App off, then turn it on again. 4. When the QR code appears, choose **Can't scan the QR code?** to reveal the text secret. 5. Copy the long secret key and store it safely before leaving the setup screen. 6. Add that key to your authenticator app if you are setting it up fresh. 7. Finish enabling 2FA on X by entering the current 6-digit code from your authenticator app. 8. Send the new long key in `totp_secret` when you call Xquik. Finish the X-side 2FA confirmation after copying the secret key. If setup is abandoned before confirmation, re-authentication cannot use that key. See [2FA secret key setup](/api-reference/x-accounts/connect#2fa-secret-key-setup) for the full connection checklist. ## Response ### 200 OK Account ID. X username. X user ID. Account status (e.g. `active`). Derived login/cookie health. One of `healthy`, `locked`, `needsReauth`, `recovering`, `suspended`, `temporaryIssue`. See [Account health](/api-reference/x-accounts/list#account-health) for meanings. ISO 8601 creation timestamp. ```json theme={null} { "id": "3", "xUsername": "elonmusk", "xUserId": "44196397", "status": "active", "health": "healthy", "createdAt": "2026-02-20T08:15:00.000Z" } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Missing required password field" } ``` Missing `password` field or invalid format. ```json theme={null} { "error": "invalid_id", "message": "Invalid account ID format" } ``` The provided account ID is not a valid format. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "account_not_found", "message": "X account not found." } ``` No account exists with this ID, or it belongs to a different Xquik account. ### 429 Login Cooldown ```json theme={null} { "error": "login_cooldown", "message": "Login is temporarily paused", "reason": "automated", "retryAfterMs": 3600000 } ``` A prior login attempt triggered a cooldown (for example, X flagged the session). Wait for `retryAfterMs` before retrying. The response includes a `Retry-After` header in seconds. ### 429 Rate Limited ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` value before starting another re-authentication request. ### 422 Login Failed ```json theme={null} { "error": "login_failed", "message": "Login failed. Check credentials and try again." } ``` X rejected the submitted password or TOTP secret. Retry with the current password and the saved Authenticator App secret key, not a 6-digit code. ```json theme={null} { "error": "passkey_required", "message": "Passkey verification is not supported. Use Authenticator app 2FA for this X account, then try again." } ``` X asked for passkey verification. Switch the account to Authenticator App 2FA, save the long TOTP secret key, finish setup on X, then re-authenticate with `totp_secret`. ### 503 Service Unavailable ```json theme={null} { "error": "service_unavailable", "message": "Service temporarily unavailable. Try again." } ``` The X re-authentication service is temporarily unavailable. Retry after a short delay. **Related:** [Get X Account](/api-reference/x-accounts/get) to check account status, or [Connect X Account](/api-reference/x-accounts/connect) if you need to add a new account instead. # X Account Email Verification API & Login Challenges Source: https://docs.xquik.com/api-reference/x-accounts/submit-challenge POST /x/account-connection-challenges/{id}/submit Submit an email verification code for an active X account login challenge. Start a fresh connect or reauthentication when it expires. See request fields. ```json theme={null} { "id": "42", "xUserId": "9876543210", "xUsername": "elonmusk", "status": "active", "health": "healthy", "createdAt": "2025-01-15T12:00:00Z" } ``` ```json theme={null} { "object": "x_account_connection_challenge", "id": "xch_8vGd8Y9JvH6dV0xA", "status": "requires_email_code", "expiresAt": "2026-05-08T12:10:00Z", "message": "Enter the email verification code to continue.", "username": "elonmusk" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "connection_challenge_inactive", "message": "Connection challenge is no longer active." } ``` ```json theme={null} { "error": "connection_challenge_expired", "message": "Verification code expired. Start again." } ``` ```json theme={null} { "error": "login_failed", "message": "Login failed. Check credentials and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "service_unavailable", "message": "Service temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
**Free** - does not consume credits Use this endpoint after [Connect X Account](/api-reference/x-accounts/connect) returns `202 Email Code Required`. Submit the one-time code from the account email inbox before `expiresAt` while the challenge is still active. This endpoint cannot reopen an expired, failed, completed, or replaced challenge. After `409`, `410`, or `422`, start [Connect X Account](/api-reference/x-accounts/connect) again for a new account. For an existing account, use [Re-authenticate X Account](/api-reference/x-accounts/reauth) with the current password and any required TOTP secret key. ## Continue the pending login Keep the `id` from the `202` response and submit the inbox code to that challenge. The challenge belongs to the same pending login attempt. Use the one-time code X sent to the account email inbox. Xquik strips spaces before submission, so `123 456` and `123456` are handled the same way. If X asks for a new email code, this endpoint returns `202` again. Keep the same flow open and submit the next inbox code before `expiresAt`. `410` means the code expired. `409` means the challenge was already completed, failed, expired, or replaced. Start [Connect X Account](/api-reference/x-accounts/connect) again for a new account, or use [Re-authenticate X Account](/api-reference/x-accounts/reauth) for an existing account. The dashboard follows the same flow: it keeps the pending row open, asks for the email code, accepts a new `202` prompt if X asks again, and refreshes the account list after the `201` response. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/account-connection-challenges/xch_8vGd8Y9JvH6dV0xA/submit \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "email_code": "123456" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/x/account-connection-challenges/xch_8vGd8Y9JvH6dV0xA/submit", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ email_code: "123456" }), }, ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/x/account-connection-challenges/xch_8vGd8Y9JvH6dV0xA/submit", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"email_code": "123456"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]string{ "email_code": "123456", }) req, err := http.NewRequest( "POST", "https://xquik.com/api/v1/x/account-connection-challenges/xch_8vGd8Y9JvH6dV0xA/submit", bytes.NewReader(body), ) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Headers Your API key. Session cookie authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Must be `application/json`. ## Path Parameters Challenge ID returned by [Connect X Account](/api-reference/x-accounts/connect). ## Body Email verification code for the pending connection. Codes from 4 to 64 characters are accepted. Spaces are stripped before submission. ## Response ### 201 Created Unique account ID. Connected X username. X user ID. Account connection status (e.g. `"active"`). Derived login/cookie health. One of `healthy`, `locked`, `needsReauth`, `recovering`, `suspended`, `temporaryIssue`. See [Account health](/api-reference/x-accounts/list#account-health) for meanings. ISO 8601 timestamp of when the account was connected. ```json theme={null} { "id": "3", "xUsername": "elonmusk", "xUserId": "44196397", "status": "active", "health": "healthy", "createdAt": "2026-02-20T08:15:00.000Z" } ``` ### 202 Email Code Required Always `x_account_connection_challenge`. Challenge ID to submit with the next email verification code. Always `requires_email_code`. ISO 8601 expiration time for the challenge. Human-readable next step. X username being connected. ```json theme={null} { "object": "x_account_connection_challenge", "id": "xch_8vGd8Y9JvH6dV0xA", "status": "requires_email_code", "expiresAt": "2026-05-08T12:10:00Z", "message": "Enter the email verification code to continue.", "username": "elonmusk" } ``` The connection still needs a valid email code. Submit the new code to the same endpoint. ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` Missing `email_code`, invalid JSON, or a code outside the accepted length. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Missing or invalid API key. ### 404 Not Found ```json theme={null} { "error": "connection_challenge_not_found", "message": "Connection challenge not found." } ``` The challenge ID does not exist or does not belong to the authenticated user. ### 409 Conflict ```json theme={null} { "error": "connection_challenge_inactive", "message": "Connection challenge is no longer active." } ``` The challenge was already completed, failed, expired, or replaced. ### 410 Expired ```json theme={null} { "error": "connection_challenge_expired", "message": "Verification code expired. Start again." } ``` Start a new account connection to receive a fresh challenge. ### 422 Login Failed ```json theme={null} { "error": "login_failed", "message": "Login failed. Check credentials and try again." } ``` The code or account login state was rejected. Start a new connection if the account requires fresh credentials. ### 429 Rate Limit Exceeded ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` Wait for the `Retry-After` header before retrying. ### 503 Service Unavailable ```json theme={null} { "error": "service_unavailable", "message": "Service temporarily unavailable. Try again later." } ``` Retry after a short delay. **Related:** [Connect X Account](/api-reference/x-accounts/connect) starts the challenge, and [List X Accounts](/api-reference/x-accounts/list) verifies the account after connection. # Create a Twitter Community with the X Community API Source: https://docs.xquik.com/api-reference/x-write/create-community POST /x/communities Create an X community from one connected account. Submit its name and description, then track its community ID, admin account, and write lifecycle state. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "create_community", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "create_community", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
## How to Create a Community on Twitter with Xquik Call `POST /x/communities` to create a community on Twitter from one connected account. This create Twitter community workflow submits a name and optional description. Xquik then tracks the write until X returns a final result. Use this route only when the account should own a new community. Use join or leave routes for an existing community. This route does not add rules, members, moderators, invitations, posts, analytics, or membership settings. Each community gives one topic a dedicated space on X. People can search for communities by name, description, or creator. They can join a community later. Each member of the community follows the chosen membership type and community rules. This X social media workflow creates the community container only. **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/communities \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: community-create-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "name": "Crypto Traders Hub", "description": "A community for crypto traders to share insights and strategies." }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/communities", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "community-create-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxhandle", name: "Crypto Traders Hub", description: "A community for crypto traders to share insights and strategies.", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/x/communities", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "community-create-1895432178065391234", }, json={ "account": "myxhandle", "name": "Crypto Traders Hub", "description": "A community for crypto traders to share insights and strategies.", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "myxhandle", "name": "Crypto Traders Hub", "description": "A community for crypto traders to share insights and strategies.", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/communities", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "community-create-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Prepare a Community Creation Request Approve the community name, description, and rules before this call. Keep the connected owner in the approval record. Create one idempotency key for the intended community. Reuse it only when the same request needs network recovery. Use a new key after any approved input changes. Community creation workflows should include: * Checking the final name for spelling and scope. * Reviewing rules with the future moderation team. * Recording the owner account and approval reference. * Saving the returned community and action identifiers. Do not submit parallel requests with different keys for one intended community. That can create multiple communities. Read the lifecycle state before exposing the new community to downstream workflows. ## Build a Reviewable Community Brief Create a short brief before calling this endpoint. Record the proposed name, description, owner account, audience, and moderation purpose. Keep that brief beside the approval record. Reviewers can then compare the submitted fields against the approved community identity. Treat the name as a lasting public label. Check spelling, capitalization, and topic scope before approval. Use the description to explain who should join. Avoid campaign dates or temporary slogans in either field. Those details age quickly and make later community discovery confusing. Choose one connected account as the accountable owner. Record its username and stable account ID when available. Confirm that the account appears in your connected-account inventory. Keep the same owner during network recovery. A different owner means a different write request. Store these creation inputs together: * Connected owner username and account ID. * Final community name and description. * Approval reference and approving operator. * Idempotency key for this exact submission. * Returned action ID and lifecycle state. This record separates approval from network recovery. Repeat the unchanged request only after a network timeout. Changed text needs a new approval and key. ## Confirm the Community Admin Account Choose the connected account before review. X assigns admin ownership to the original creator. Community admins manage names, descriptions, rules, and moderators in X. Xquik only sends the creation request. X currently requires an eligible admin account. Its official moderator playbook lists these checks: * Use a public account. * Use an account that is at least 6 months old. * Verify an email address or phone number. * Keep the account compliant with the X Terms of Service. X enforces these requirements and can change them. The request has no eligibility override. It also has no Premium field. Confirm current eligibility inside X before approval. Preserve any rejection reason for the operator. Review the [X Communities moderator playbook](https://help.x.com/en/using-x/communities-moderator-playbook) before assigning the owner. It explains creator, admin, moderator, and member responsibilities. Store the chosen account with the approved community brief. ## Plan Community Rules and Membership Define community rules before creating the container. Rules should describe the topic, allowed behavior, and moderation response. Do not place temporary campaign instructions in the public description. Keep the full rule set in the team's approved moderation record. X offers open communities and restricted membership. Open communities show a Join button at the top of the community page. Restricted communities can let a person request to join. A moderator then accepts or denies that request. X may also allow member invitations. This endpoint does not choose a membership type. It does not add community members or moderators. Configure those settings after creation through the current X interface. Record the selected mode beside the returned community ID. Read the [X Communities guide](https://help.x.com/en/using-x/communities) before opening membership. It covers visibility, roles, invitations, and moderation. Use the guide as the source for current X behavior. Use this page for Xquik's API contract. ## Prepare a Brand or Product Community Use a specific name that explains the shared topic. Write a description that identifies the intended community members. State who operates the community. Avoid names that imply an unsupported partnership or endorsement. Prepare the first discussion topics before launch. Assign community admins and moderators before invitations begin. Decide who reviews reports and removes disruptive members. Keep escalation rules outside the public description. This route cannot publish welcome posts or schedule discussions. It cannot promote or monetize the community. After launch, publish Community posts with [Create Tweet](/api-reference/x-write/create-tweet). X may display Community posts inside a member's Twitter feed. ## Connect the Community to a Website or App The request has no website, app, rules, or invitation field. Store those links in your application after X confirms creation. Associate them with the returned community ID, owner account, approval, and lifecycle record. After success, verify the name on the community page. Search for communities by name, description, or creator through X when available. X notes that not every community appears in search results. Do not treat search visibility as proof of creation. Use the community ID as the stable integration key. Do not key integrations by the editable display name. Store a verified community URL only after the read route confirms the new ID. ## Answer Common Community Creation Questions ### Do X Communities Still Exist? Yes. X still publishes current Communities and moderator guidance. This API creates an X Community through a connected account. X controls feature availability and account eligibility. ### Do You Need X Premium to Create a Community? The Xquik request does not accept a Premium setting. X decides whether the connected account can create the community. Check current X eligibility before submitting a billable write. ### Can the API Add Rules, Members, or Moderators? No. The canonical request accepts `account`, `name`, and `description`. Set community rules, membership type, invitations, and moderator roles after creation. Never send undocumented fields. ### Can the API Schedule, Grow, or Monetize a Community? No. This route creates the community and tracks that write. It does not schedule posts, invite initial members, run promotions, provide analytics, or configure monetization. ### How Should Software Handle Community Creation? Require an approved brief and one idempotency key. Store the action ID immediately. Poll the lifecycle, verify the new community ID, and then start separate membership or publishing workflows. ## Validate the New Community Wait for the write lifecycle to finish before announcing the community. A `202` response means Xquik accepted the write for processing. It does not prove that X completed community creation. Follow the returned lifecycle state until the action reaches its final result. Capture the new community ID from the confirmed result. Then call [Community Info](/api-reference/x/community-info). Compare its name and description with the approved brief. Save the returned community ID beside the owner account and action ID. Run these checks before inviting members: 1. Confirm the community ID resolves. 2. Compare the returned name with the approved name. 3. Compare the returned description with the approved description. 4. Confirm the intended owner account remains connected. 5. Record the final write state and verification time. Repeat the information request after a temporary read failure. Keep the same key only when every input remains unchanged. ## Handle Creation Failures Safely Treat each response by its actual cause. Fix invalid names or missing accounts before sending another write. Restore credits before retrying a `402` response. Reconnect the owner after `403`. A `409` means the key already identifies another request. Compare both request bodies. Reuse the key only when every creation input matches. Generate a new key after any approved field changes. For `422`, preserve the rejected request and returned reason. Do not rewrite the community brief automatically. Return it to the operator for review. For `429`, respect the retry guidance and keep the same approved intent. After `503`, check the write lifecycle before retrying. A disconnected client does not prove failure. Duplicate creation costs more than delayed verification. | Community creation record | Request or response source | Completion rule | | ------------------------- | -------------------------------- | ------------------------------ | | Owner | Request `account` | Match the connected owner. | | Name | Request `name` | Keep the approved name. | | Purpose | Request `description` | Keep the approved description. | | Replay key | Request header | Reuse it for one exact replay. | | Action ID | Response `id` | Poll this lifecycle record. | | State | Response `status` and `terminal` | Stop after a terminal result. | | Community ID | Response `communityId` | Store it after success. | | Verification | `GET /x/communities/{id}/info` | Match the name and owner. | ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Body The connected X account to create the community as. Must be a username you have connected to your Xquik account. The name for the new community. Optional description for the community explaining its purpose. ## Response Connect the requested account, then submit a newly approved write. ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Post API for Tweet & Reply Automation Source: https://docs.xquik.com/api-reference/x-write/create-tweet POST /x/tweets Post tweets and replies from a connected X account with public image URLs or 1 MP4 video URL, write-status polling, and audit handoff. See action fields. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "create_tweet", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "create_tweet", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**30 credits text-only** · attached media adds 2 credits per started MB across all files Create a tweet or reply from one connected X account. This Twitter API for posting tweets accepts text and public media URLs. Put public HTTPS images or one MP4 URL in `media`. When `POST /x/media` hosts a local file, use its `mediaUrl`. Never send `mediaId` or `media_ids` to this endpoint. Send a unique `Idempotency-Key`. Store the durable action. Poll `statusUrl` while `terminal` is `false`. X's [Create Post guide](https://docs.x.com/x-api/posts/create-post) covers its separate endpoint. Use Xquik authentication. Store its durable write fields. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/tweets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: tweet-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "elonmusk", "text": "Hello from Xquik!" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/tweets", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "tweet-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "elonmusk", text: "Hello from Xquik!", }), }); const result = await response.json(); if (!response.ok) { throw new Error(JSON.stringify(result)); } const postRecord = { status: result.status, terminal: result.terminal, safe_to_retry: result.safeToRetry, write_action_id: result.id, request_hash: result.request.hash, tweet_id: result.result?.id ?? result.tweetId ?? null, account: result.account, target: result.target, charged: result.billing.charged, charged_credits: result.billing.chargedCredits, poll_path: result.terminal ? null : result.statusUrl, }; process.stdout.write(`${JSON.stringify(postRecord)}\n`); ``` ```python Python theme={null} import requests import json response = requests.post( "https://xquik.com/api/v1/x/tweets", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "tweet-1895432178065391234", }, json={ "account": "elonmusk", "text": "Hello from Xquik!", }, ) result = response.json() response.raise_for_status() post_record = { "status": result["status"], "terminal": result["terminal"], "safe_to_retry": result["safeToRetry"], "write_action_id": result["id"], "request_hash": result["request"]["hash"], "tweet_id": (result.get("result") or {}).get("id") or result.get("tweetId"), "account": result["account"], "target": result["target"], "charged": result["billing"]["charged"], "charged_credits": result["billing"]["chargedCredits"], "poll_path": None if result["terminal"] else result["statusUrl"], } print(json.dumps(post_record)) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" ) type CreateTweetResponse struct { ID string `json:"id"` TweetID string `json:"tweetId"` Status string `json:"status"` Terminal bool `json:"terminal"` SafeToRetry bool `json:"safeToRetry"` StatusURL string `json:"statusUrl"` Request struct { Hash *string `json:"hash"` } `json:"request"` Billing struct { Charged bool `json:"charged"` ChargedCredits string `json:"chargedCredits"` } `json:"billing"` Result *struct { ID string `json:"id"` } `json:"result"` } func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "elonmusk", "text": "Hello from Xquik!", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/tweets", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "tweet-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode >= 400 { body, _ := io.ReadAll(resp.Body) panic(string(body)) } var result CreateTweetResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } var tweetID any if result.Result != nil { tweetID = result.Result.ID } else if result.TweetID != "" { tweetID = result.TweetID } var pollPath any if !result.Terminal { pollPath = result.StatusURL } postRecord := map[string]any{ "status": result.Status, "terminal": result.Terminal, "safe_to_retry": result.SafeToRetry, "write_action_id": result.ID, "request_hash": result.Request.Hash, "tweet_id": tweetID, "account": "elonmusk", "charged": result.Billing.Charged, "charged_credits": result.Billing.ChargedCredits, "poll_path": pollPath, } encoded, err := json.Marshal(postRecord) if err != nil { panic(err) } fmt.Println(string(encoded)) } ``` ## Choose the Twitter Post Request Choose among 6 tweet request formats. Include only the fields required below. | Post intent | Required fields | Validation before sending | | --------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------- | | Standard tweet | `account` and `text` | Keep standard tweet text at 280 characters or fewer. | | Tweet reply | `account`, `text`, and `reply_to_tweet_id` | Store the parent Tweet ID and confirm the connected account may reply. | | Community tweet | `account`, `text`, and `community_id` | Check the account can post in that X Community. | | Image tweet | `account`, optional `text`, and 1 to 4 image URLs in `media` | Use public HTTPS JPEG, PNG, GIF, WebP, or AVIF URLs. | | Video tweet | `account`, optional `text`, and 1 MP4 URL in `media` | Keep the public MP4 at 100 MB or less. Do not mix video and images. | | Note tweet | `account`, `text`, and `is_note_tweet: true` | Keep note tweet text at 25,000 characters or fewer. | Create one `Idempotency-Key` for each automatic Twitter posting request. Reuse that key only when replaying the same network request. A new tweet, reply, caption, media URL, account, or community requires a new key. This endpoint starts the write immediately. It does not schedule future delivery. Let your scheduler call it at the approved time. One request publishes 1 tweet on X. Create replies with `reply_to_tweet_id`. Create each thread tweet with a separate approved request. ## Store the Tweet Write Receipt Persist the write receipt before another worker posts. Keep these field groups: * Store `id` and `request.hash` for request matching. * Store `account` and `target` for the selected account and destination. * Store `status`, `terminal`, and `statusUrl` for polling. * Store `safeToRetry` for retry decisions. * Store `result.result?.id` or `tweetId` for the published Tweet ID. * Store `billing.chargedCredits` for billing checks. ## Post With Public Media URLs Use `media` for an image or MP4 at a public HTTPS URL. For local files, call [Upload Media](/api-reference/x-write/upload-media) first. Pass its returned `mediaUrl` in `media`. Send up to 4 image URLs or exactly 1 MP4 URL. Keep the MP4 at 100 MB or less. Never send `media_ids`; that field is for DMs only. Attached media adds 2 credits per started MB across all files. ```bash Image tweet theme={null} curl -X POST https://xquik.com/api/v1/x/tweets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: image-tweet-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "brand_account", "text": "Launch notes are live.", "media": ["https://cdn.example.com/product-screenshot.png"] }' | jq ``` ```bash Image reply theme={null} curl -X POST https://xquik.com/api/v1/x/tweets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: image-reply-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "brand_account", "text": "Here is the chart.", "reply_to_tweet_id": "1893456789012345678", "media": ["https://cdn.example.com/reply-chart.png"] }' | jq ``` ```bash MP4 video tweet theme={null} curl -X POST https://xquik.com/api/v1/x/tweets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: video-tweet-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "brand_account", "text": "Launch walkthrough is live.", "media": ["https://cdn.example.com/product-demo.mp4"] }' | jq ``` Store `id`, `request.hash`, `billing`, `result`, `reply_to_tweet_id`, and `media`. Poll [Get Write Action Status](/api-reference/x-write/get-write-action-status) while `terminal` is `false`. Retry only when `safeToRetry` is `true`, using a new key. ## Headers Send your Xquik API key in this header. Alternatively, send an OAuth 2.1 bearer token. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Body Choose the connected X account by username or account ID. Xquik removes an optional `@` prefix. Send up to 280 characters for a standard tweet. Note tweets accept up to 25,000 characters. Omit text only when `media` exists. Set the parent Tweet ID. Xquik posts the new tweet inside that thread. Set the target X Community ID. Confirm that the connected account is a member. Set `true` for a note tweet with up to 25,000 characters. The default is `false`. Attach public media URLs directly. * Send up to 4 JPEG, PNG, GIF, WebP, or AVIF image URLs. * Send exactly 1 public MP4 URL up to 100 MB. * Never mix video with other media. * Use [Upload Media](/api-reference/x-write/upload-media) to host a local file. * Pass its returned `mediaUrl` in `media`. * Never pass uploaded `mediaId` values or `media_ids`. * Attached media adds 2 credits per started MB across all files. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # How to Delete a Community on Twitter via API Source: https://docs.xquik.com/api-reference/x-write/delete-community DELETE /x/communities/{id} Delete an owned X community by ID and confirmed name. Archive its tweets, members, moderators, and rules, then verify the final write lifecycle state. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "delete_community", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "delete_community", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
## How to Delete a Twitter Community Through REST Call `DELETE /x/communities/{id}` for one community owned by a connected account. Send its exact `community_name` as a confirmation safeguard. This route removes the community, not the connected X account. This route explains how to delete a Twitter Community through one tracked deletion action. It also shows how to delete Twitter Community records safely. Use [Leave Community](/api-reference/x-write/leave-community) to remove one account's membership. Use [Delete Tweet](/api-reference/x-write/delete-tweet) to remove one owned Community post. **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/x/communities/1893726451023847424 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: community-delete-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "community_name": "Crypto Traders Hub" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/x/communities/1893726451023847424", { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "community-delete-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxhandle", community_name: "Crypto Traders Hub", }), } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.delete( "https://xquik.com/api/v1/x/communities/1893726451023847424", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "community-delete-1895432178065391234", }, json={ "account": "myxhandle", "community_name": "Crypto Traders Hub", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "myxhandle", "community_name": "Crypto Traders Hub", }) req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/x/communities/1893726451023847424", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "community-delete-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## How to Delete Twitter Community Records Safely Confirm the community ID, owner, exact name, reason, and approver. Create one `Idempotency-Key` for that deletion. Reuse it only during unchanged network recovery. Xquik has no restore route. Plan for permanent removal. Archive required records before approval: * Save the name, description, rules, creator, and policies from [Community Info](/api-reference/x/community-info). * Follow every [Community Tweets](/api-reference/x/community-tweets) cursor. Store tweet IDs, authors, text, timestamps, media URLs, replies, reposts, and likes. * Follow every [Community Members](/api-reference/x/community-members) cursor. Store stable user IDs and the final `hasMore` value. * Export [Community Moderators](/api-reference/x/community-moderators) separately. Member rows do not prove moderator roles. X says Community posts remain after their Community is deleted. Review the [X Communities guide](https://help.x.com/en/using-x/communities) before removal. Keep your archive even when those posts remain visible elsewhere. ## Verify Permanent Community Deletion Store the action ID after `200` or `202`. Poll while `terminal` is `false`. After terminal success, call [Community Info](/api-reference/x/community-info). An unavailable community confirms removal. A temporary read failure does not. Repeat the read before starting another deletion. ## Fix Twitter Community Deletion Errors Fix invalid fields after `400`. Replace credentials after `401`. Add credits after `402`. Reconnect the owner after `403`. Verify the account and community after `404`. Resolve key conflicts after `409`. Review rejected requests after `422`. Honor `Retry-After` after `429`. Check `safeToRetry` after `500` or `503`. ## Delete Twitter Community Questions ### Can You Delete a Twitter Community? Yes. Send the community ID and exact name through the connected owner. Xquik then returns a tracked write action. ### Does Deletion Affect the Owner's X Profile? No profile field appears in the request body. The route targets one community ID. It does not disconnect or delete the owner's X account. ### Can a Deleted Community Be Restored? Xquik documents no restore endpoint. Archive first and treat deletion as permanent within this API workflow. ### Can I Transfer Ownership Instead? This route has no ownership-transfer field. Complete any supported transfer in X before deletion, then verify the intended owner. ### What Happens to Members and Community Posts? Members lose access to the deleted Community. X says existing Community posts are not deleted with the Community. Archive required posts before removal. Use Delete Tweet for an owned post. ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended write. Reuse it only when every field remains unchanged after interruption. Must be `application/json`. ## Path parameters The ID of the community to delete. ## Body The connected X account that owns the community. Must be a username you have connected to your Xquik account. The exact community name. Send it to avoid deleting a different Community. ## Response Connect the requested account, then submit a newly approved write. ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter API Delete Tweet: Remove an Owned Post by ID Source: https://docs.xquik.com/api-reference/x-write/delete-tweet DELETE /x/tweets/{id} Use the Twitter API delete tweet workflow to remove one owned post by ID. Save approval evidence, track every response, and verify the deleted Tweet ID. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "delete_tweet", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "delete_tweet", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "x_rate_limited", "message": "Rate limited by X. Wait before retrying." } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Use this Twitter API delete tweet route to remove 1 owned post by ID. This delete tweet API accepts 1 Tweet ID per request. Select the connected X account that published the tweet. Save approval evidence before sending the request. Send 1 unique `Idempotency-Key` for the intended deletion. Store the durable write action and poll `statusUrl` when needed. Confirm `targetId` matches the requested Tweet ID after terminal success. X also documents an official [Delete Post endpoint](https://docs.x.com/x-api/posts/delete-post). Authenticate with Xquik. Store its durable write response. ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/x/tweets/1895432178065391234 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: tweet-delete-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "elonmusk" }' | jq '{ status, terminal, safe_to_retry: .safeToRetry, write_action_id: .id, request_hash: .request.hash, requested_tweet_id: "1895432178065391234", confirmed_tweet_id: (.result.id // .tweetId // .targetId), charged_credits: .billing.chargedCredits, poll_path: (if .terminal then null else .statusUrl end) }' ``` ```javascript Node.js theme={null} const tweetId = "1895432178065391234"; const response = await fetch(`https://xquik.com/api/v1/x/tweets/${tweetId}`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "tweet-delete-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "elonmusk", }), }); const result = await response.json(); if (!response.ok) { throw new Error(JSON.stringify(result)); } const deletionRecord = { status: result.status, terminal: result.terminal, safe_to_retry: result.safeToRetry, write_action_id: result.id, request_hash: result.request.hash, requested_tweet_id: tweetId, confirmed_tweet_id: result.result?.id ?? result.tweetId ?? result.targetId ?? null, account: result.account, charged_credits: result.billing.chargedCredits, poll_path: result.terminal ? null : result.statusUrl, }; process.stdout.write(`${JSON.stringify(deletionRecord)}\n`); ``` ```python Python theme={null} import json import requests tweet_id = "1895432178065391234" response = requests.delete( f"https://xquik.com/api/v1/x/tweets/{tweet_id}", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "tweet-delete-1895432178065391234", }, json={ "account": "elonmusk", }, ) result = response.json() response.raise_for_status() deletion_record = { "status": result["status"], "terminal": result["terminal"], "safe_to_retry": result["safeToRetry"], "write_action_id": result["id"], "request_hash": result["request"]["hash"], "requested_tweet_id": tweet_id, "confirmed_tweet_id": ( (result.get("result") or {}).get("id") or result.get("tweetId") or result.get("targetId") ), "account": result["account"], "charged_credits": result["billing"]["chargedCredits"], "poll_path": None if result["terminal"] else result["statusUrl"], } print(json.dumps(deletion_record)) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" ) type DeleteTweetResponse struct { ID string `json:"id"` Status string `json:"status"` Terminal bool `json:"terminal"` SafeToRetry bool `json:"safeToRetry"` StatusURL string `json:"statusUrl"` TargetID *string `json:"targetId"` TweetID *string `json:"tweetId"` Request struct { Hash string `json:"hash"` } `json:"request"` Account map[string]any `json:"account"` Billing struct { ChargedCredits string `json:"chargedCredits"` } `json:"billing"` Result *struct { ID string `json:"id"` } `json:"result"` } func main() { tweetID := "1895432178065391234" body, err := json.Marshal(map[string]string{ "account": "elonmusk", }) if err != nil { panic(err) } req, err := http.NewRequest( "DELETE", "https://xquik.com/api/v1/x/tweets/"+tweetID, bytes.NewReader(body), ) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "tweet-delete-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode >= 400 { responseBody, _ := io.ReadAll(resp.Body) panic(string(responseBody)) } var result DeleteTweetResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } var confirmedTweetID any if result.Result != nil && result.Result.ID != "" { confirmedTweetID = result.Result.ID } else if result.TweetID != nil { confirmedTweetID = *result.TweetID } else if result.TargetID != nil { confirmedTweetID = *result.TargetID } var pollPath any if !result.Terminal { pollPath = result.StatusURL } deletionRecord := map[string]any{ "status": result.Status, "terminal": result.Terminal, "safe_to_retry": result.SafeToRetry, "write_action_id": result.ID, "request_hash": result.Request.Hash, "requested_tweet_id": tweetID, "confirmed_tweet_id": confirmedTweetID, "account": result.Account, "charged_credits": result.Billing.ChargedCredits, "poll_path": pollPath, } encoded, err := json.Marshal(deletionRecord) if err != nil { panic(err) } fmt.Println(string(encoded)) } ``` ## Delete One Owned Tweet by ID Send the exact Tweet ID in the path. Send its author account in the request body. The connected account must own the target tweet. You cannot delete another account's tweet with this route. A reply is also a tweet with its own ID. Delete the reply ID without deleting its parent tweet. Use [Get Tweet](/api-reference/x/get-tweet) before deletion. Save the returned author, text, creation time, media, and conversation ID. Create a dedicated idempotency key for each intended deletion. Reuse that key only after an interrupted response. Never reuse a create-tweet key for deletion. ## Authenticate the Tweet Owner Send an Xquik API key or OAuth 2.1 bearer token. Then identify the connected X account with `account`. The request never accepts an X password or session cookie. Keep API keys on your server, not inside browser or mobile clients. The official X endpoint uses a user access token. Its permissions differ from Xquik authentication. Read X's [Manage Posts guide](https://docs.x.com/x-api/posts/manage-tweets/introduction) for that separate contract. ## Save Tweet Deletion Evidence This API workflow cannot restore a deleted tweet. Show the final tweet text during approval. Keep the approver, approval time, and reason beside the Tweet ID. Persist these fields after sending the request: * Store `id` as the durable write action ID. * Store `request.hash` for exact request matching. * Store `account` for the connected tweet owner. * Store `targetId` for the confirmed Tweet ID. * Store `status`, `terminal`, and `statusUrl` for polling. * Store `billing.chargedCredits` for the deletion charge. * Store `safeToRetry` before considering another request. A replacement tweet receives a different Tweet ID. Keep the deleted ID in the audit record. Join replies, analytics, and moderation records with the deleted ID. ## Run Bulk or Scheduled Tweet Cleanup This route deletes 1 tweet per request. It does not provide a bulk-delete operation. Feed approved Tweet IDs through the route individually. Create a unique `Idempotency-Key` for every Tweet ID. Delete tweets in a controlled queue. Store every write action. After HTTP `429`, wait for `Retry-After` before continuing. X publishes separate limits for its [Delete Post endpoint](https://docs.x.com/x-api/fundamentals/rate-limits). Those limits do not replace Xquik's response headers. This endpoint does not schedule future deletion. Let your scheduler call it at the approved time. Keep each scheduled Tweet ID and approval reference together. ## Verify Tweet Deletion A `202` response means the deletion is still active. Poll `statusUrl` until `terminal` becomes `true`. Never submit another deletion while the action remains active. After terminal success, accept `targetId`, `tweetId`, or `result.id` as confirmation. Call [Get Tweet](/api-reference/x/get-tweet) with that ID. Store the lookup time and result beside the write action. Save the unavailable lookup beside the successful write action. Before terminal success, an unavailable lookup proves nothing. A missing tweet does not always prove deletion. ## Handle Delete Tweet Errors Never infer failure from a client timeout. Poll the returned action before sending another request. Use [Twitter API error handling](/api-reference/x-write/get-write-action-status) for every lifecycle field and retry rule. * For `400`, fix the Tweet ID or account value. * For `401`, replace invalid Xquik authentication. * For `402`, add credits before another deletion. * For `403`, reconnect the selected X account. * For `409`, keep the original action and payload. * For `422`, review the rejection before retrying. * For `429`, wait for `Retry-After`. * For `500` or `503`, trust `safeToRetry` and `terminal`. ## Choose Tweet Deletion, Unlike, or Unretweet Use tweet deletion only when the connected account owns the published post. It removes that post. It does not merely reverse an engagement action. Use [Unlike Tweet](/api-reference/x-write/unlike) to remove one account's like. Use [Unretweet](/api-reference/x-write/unretweet) to remove one account's repost. Both actions preserve the source tweet. | Cleanup intent | API route | What changes | | -------------------- | ------------------------------- | -------------------------------------------------- | | Remove an owned post | `DELETE /x/tweets/{id}` | The connected account's tweet becomes unavailable. | | Remove a like | `DELETE /x/tweets/{id}/like` | Only that account's like is removed. | | Remove a repost | `DELETE /x/tweets/{id}/retweet` | Only that account's repost is removed. | | Remove a reply | `DELETE /x/tweets/{id}` | The owned reply tweet is deleted by its own ID. | Never delete the source tweet when a campaign only needs engagement cleanup. Check the route during approval. Record the intended state. ## Twitter API Delete Tweet Questions ### Can the API delete another user's tweet? No. The connected account must own the target tweet. Use the author's own connected account and exact Tweet ID. ### Can the API delete all tweets at once? No bulk route exists here. Submit 1 approved Tweet ID per request. Preserve each deletion's key, write action, and terminal result. ### Can I schedule a tweet deletion? Yes, through your own scheduler. Call this endpoint at the approved time. The endpoint itself does not store a future schedule. ### Which libraries can delete a tweet? Any HTTP client can call this REST endpoint. The examples cover cURL, Node.js, Python, and Go. Keep credentials in server-side code. ### How should recurring tweet cleanup work? Select owned Tweet IDs using an approved retention policy. Snapshot each tweet before deletion. Send one deletion, store its action, and verify its terminal result. ## Headers Send your Xquik API key in this header. Alternatively, send an OAuth 2.1 bearer token. Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters The ID of the tweet to delete. Must be a tweet owned by the specified connected account. ## Body X username or account ID identifying which connected X account owns the tweet. The `@` prefix is automatically stripped if included. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Follow API: Follow One X User by ID Source: https://docs.xquik.com/api-reference/x-write/follow POST /x/users/{id}/follow Use the Twitter Follow API to follow one user by ID. Approve the connected account, poll the write action, handle limits, and verify the relationship. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "follow", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "follow", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
## Follow One Twitter User by ID Use this Twitter API follow endpoint for 1 target user ID. Each Twitter API follow user request names 1 connected X account. Verify the target user ID. Approve the acting account before sending the action. Compare X's separate [Follow User endpoint](https://docs.x.com/x-api/users/follow-user). **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit **Action-specific rate limit:** Follow allows **20 requests per minute** and **400 per day** per account. The general write tier allows 120 per 60s. Exceeding either limit returns `429 Too Many Requests`. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/users/44196397/follow \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: follow-44196397-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxaccount" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/users/44196397/follow", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "follow-44196397-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxaccount", }), }); const followReceipt = await response.json(); if (!response.ok) { throw new Error(JSON.stringify(followReceipt)); } ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/x/users/44196397/follow", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "follow-44196397-1895432178065391234", }, json={ "account": "myxaccount", }, ) follow_receipt = response.json() response.raise_for_status() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "myxaccount", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/users/44196397/follow", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "follow-44196397-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var followReceipt map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&followReceipt); err != nil { panic(err) } fmt.Println(followReceipt) } ``` ## Authenticate and Approve the Follow Send an Xquik API key or OAuth 2.1 bearer token. Never send an X password or session cookie. Show the target user ID, username, profile name, and acting account. Use the URL `https://xquik.com/api/v1/x/users/{id}/follow` in Python. Start the sample with `import requests`. ## Verify the Twitter Follow API Result Store the target ID, acting account, action ID, and request hash. Poll `statusUrl` until `terminal` becomes `true`. Close already-followed results without another request. Use [Check Follower](/api-reference/x/check-follower) when current proof matters. Use [Following](/api-reference/x/following) to read outbound relationships. Use [Unfollow User](/api-reference/x-write/unfollow) to reverse approval. ## Handle Twitter Follow API Errors Fix the user ID or account after `400`. Replace Xquik authentication after `401`; reconnect after `403`. Add credits after `402`; review rejected input after `422`. Honor `Retry-After` after `429`. After `500` or `503`, check `safeToRetry` before retrying. ## Twitter API Follow Questions ### How Do I Follow a User Programmatically? Approve the target user ID and connected account. Send this POST request; poll the returned action until terminal. ### Can I Follow a Protected X Account? Protected X profiles may keep follows pending until approval. Verify the relationship before dependent work begins. ### Can I Bulk Follow Twitter Users? No bulk request exists here. Send 1 approved target ID per action and respect both limits above. ### Do I Need a Twitter Follow SDK? No. Use any HTTP client or the cURL, Node.js, Python, and Go examples. A third-party app still needs an approved connected account. ### How Do I Check or Undo a Follow? Use Check Follower for current proof. Use Unfollow User with new approval and a new idempotency key. ## Headers Send your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters The X user ID of the account to follow. ## Body X username or account ID of your connected account to act as. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter API Errors & X Write Action Status Source: https://docs.xquik.com/api-reference/x-write/get-write-action-status GET /x/write-actions/{id} Handle Twitter API errors by polling tweet, reply, DM, follow, like, repost, media, profile, and community writes. Check results, billing, and safe retries. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "like", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "create_tweet", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ```
For the complete documentation index, see llms.txt.
Every X write returns a durable `x_write_action` record. Store its `id` or `writeActionId`. Poll `statusUrl` whenever `terminal` is `false`. ## Handle Twitter API Errors Use this endpoint for Twitter API error handling after an Xquik write. Xquik adds a durable write record beside ordinary HTTP response codes. An error message does not prove that a write failed. A timeout can occur after dispatch. Trust `terminal`, `safeToRetry`, `sendDispatched`, and `nextAction`. Review X's [response codes and errors](https://docs.x.com/x-api/fundamentals/response-codes-and-errors). Xquik's write status record uses different fields. Trust `terminal`, `safeToRetry`, and `nextAction`. Never infer retry safety from an HTTP status or error name alone. ## Agent Algorithm 1. Generate one unique `Idempotency-Key` for the intended write. 2. Submit the write once. 3. Store `id`, `request.hash`, `account`, `target`, `billing`, and `statusUrl`. 4. When `terminal` is `false`, wait for `Retry-After` or `pollAfterMs`. 5. Poll `statusUrl` until `terminal` is `true`. 6. Record `result` and settled `billing`. 7. Retry only when `safeToRetry` is `true`, using a new key. 8. When `nextAction.type` is `verify_result`, verify the external state first. Reuse the original `Idempotency-Key` only to replay the same account, action, target, and payload. The replay returns the same durable action. ## Lifecycle | Status | Terminal | Required behavior | | ---------------------- | -------- | -------------------------------------------------- | | `accepted` | No | Poll. Dispatch has not completed. | | `dispatching` | No | Poll. Do not submit another write. | | `pending_confirmation` | No | Poll. The write may already exist. | | `success` | Yes | Store the result and settled billing. | | `failed` | Yes | Follow `safeToRetry` and `nextAction`. | | `expired` | Yes | Verify the result when dispatch may have occurred. | Active actions return `202`, `Location`, and `Retry-After`. Terminal actions return `200` from this endpoint. ## Request ```bash cURL theme={null} curl https://xquik.com/api/v1/x/write-actions/42 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/x/write-actions/42", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ); const action = await response.json(); if (!action.terminal) { const delayMs = action.pollAfterMs ?? 2000; process.stdout.write( JSON.stringify({ delayMs, next: action.statusUrl }) + "\n", ); } else if (action.safeToRetry) { process.stdout.write("Ask for approval, then retry with a new key.\n"); } else { process.stdout.write(JSON.stringify(action.result) + "\n"); } ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/x/write-actions/42", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) action = response.json() if not action["terminal"]: print({"delay_ms": action["pollAfterMs"], "next": action["statusUrl"]}) elif action["safeToRetry"]: print("Ask for approval, then retry with a new key.") else: print(action["result"]) ``` ## Headers Send your Xquik API key in this header. Send an OAuth 2.1 bearer token instead of `x-api-key`. ## Path Parameters Durable action ID returned by the original write. ## Response Polling is complete. Store `result` and settled `billing`. Follow `safeToRetry` and `nextAction` before any new attempt. Poll `statusUrl` after `Retry-After` or `pollAfterMs`. Never submit another write while `terminal` is `false`. Fix the authentication credentials. Keep the original write record. Check the action ID and environment. Never resubmit the original write. Wait for `Retry-After`. Poll the same action again. Never resend the write. Always `x_write_action`. Durable action ID. This field aliases `id`. Exact write operation. Current lifecycle status. This field is `true` when polling can stop. This field is `true` when a later attempt could succeed. This field is `true` when a new attempt is safe. Relative polling URL. Recommended polling delay. `true` after billing settles as charged. Settled credits charged. Planned and settled billing state. Stable hash and exact sanitized payload. Connected account selected for this write. Target type and ID. Target ID alias. Confirmed result or desired state. Required poll, retry, or verification step. Stable request fingerprint. Correlation ID echoed in `X-Request-Id`. `true` when this response replays an action. Machine-readable error code. Actionable status or error message. This field is `true` after dispatch. ISO 8601 dispatch time. ISO 8601 creation time. ISO 8601 latest update time. ISO 8601 terminal time. Nonterminal resolution deadline. ISO 8601 confirmation time. ISO 8601 latest confirmation check. Confirmation attempt count. Confirmed tweet ID when available. Confirmed direct message ID when available. Confirmed media ID when available. Public media URL when available. Confirmed community ID when available. Confirmed community name when available. Result ID alias. Media details when used. Structured recovery context. This field is `true` when status equals `success`. ```json theme={null} { "object": "x_write_action", "id": "42", "writeActionId": "42", "action": "create_tweet", "status": "pending_confirmation", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/42", "pollAfterMs": 2000, "billing": { "status": "pending", "charged": false, "plannedCredits": "30", "chargedCredits": "0" }, "request": { "hash": "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", "payload": { "text": "Hello" } }, "account": { "id": "8", "username": "agent_account" }, "target": null, "result": null, "nextAction": { "type": "poll", "url": "/api/v1/x/write-actions/42", "afterMs": 2000 }, "sendDispatched": true, "success": false } ``` ## Retry Rules * `retryable: false`, `safeToRetry: false`: do not retry. * `retryable: true`, `safeToRetry: false`: verify the result first. * `safeToRetry: true`: request approval and retry using a new key. * `terminal: false`: poll, even when the original HTTP response was an error. Idempotency replay protection remains active for at least 90 days. # How to Join a Community on Twitter via API Source: https://docs.xquik.com/api-reference/x-write/join-community POST /x/communities/{id}/join Learn how to join a Twitter Community by ID with one connected account. Track API responses, verify membership, and resolve invitation or eligibility failures. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "join_community", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "join_community", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/communities/1893726451023847424/join \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: community-join-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/x/communities/1893726451023847424/join", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "community-join-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxhandle", }), } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/x/communities/1893726451023847424/join", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "community-join-1895432178065391234", }, json={ "account": "myxhandle", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "myxhandle", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/communities/1893726451023847424/join", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "community-join-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## How to Join a Community on Twitter Through the API Call `POST /x/communities/{id}/join` with one connected Twitter account and numeric ID. X decides whether that account can join. The route cannot discover, create, or moderate Communities. Approve each pair first. ## Understand Open, Restricted, and Invited Membership Open Communities allow direct joins. Restricted Communities may require approval or an invitation. X controls each membership mode. Open the Community page and review its rules. Use the join button for manual membership. Find it at the top of the page. Restricted Communities display Ask to join. A moderator decides that request. No API field selects a mode or accepts an invitation. Use the official [X Communities guide](https://help.x.com/en/using-x/communities) for current interface behavior. ## Find Twitter Communities Before Joining Existing members can open the Community tab on X.com or iOS. Search by name, description, creator, or handle. X warns that some Communities never appear in search results. Use a direct URL for an account's first Community. Ask an owner or moderator for the exact link when search omits it. Confirm the specific Community name, description, rules, moderators, membership mode, and recent posts. Never approve membership from a similar display name. Match the numeric ID with [Community Info](/api-reference/x/community-info). [Community Search](/api-reference/x/community-search) searches posts inside one known Community. It cannot find Community directories or topic-based groups. ## Compare Manual and API Community Joining For manual membership, sign in to X and open the Community URL. Review the Community rules and membership mode. Select Join or Ask to join. Then wait for any required moderator review. For an API workflow, validate the connected Twitter account and numeric ID. Capture approval before submission. Poll the returned action. Verify the complete member roster after the action reaches a terminal state. X handles invitations, rules, and moderator review for both paths. The API cannot click controls, select a membership mode, or bypass moderator approval. ## Review Account Eligibility and Moderator Limits Confirm that the X account remains public and connected. Check existing membership and pending requests before submission. Reusing an existing action prevents duplicate membership requests. Admins and moderators enforce each Community's rules. They review restricted requests. A block between the account and a moderator can stop membership. The API cannot override that relationship. X says an admin account must have a verified email address or phone number. This verified-contact requirement applies only to admins. Twitter Communities offer topic-focused spaces for members. Members use a dedicated space for one shared topic. The contract defines no separate community account. Store the connected account and Community ID as separate fields. ## Choose the Correct X Relationship * Use Join Community for one approved membership. * Use Follow for one account relationship. * Use Create Tweet for an approved Community post. * Use Community Members to verify the roster. * Use Community Tweets to read the Community timeline. Following, Lists, group messages, and Community Notes cannot change membership. ## Plan Post-Join Reading and Publishing Joining adds one connected account to one social media Community. It does not create posts, invitations, or moderator changes. Read [Community Tweets](/api-reference/x/community-tweets) after confirmation. Store Tweet IDs, replies, reposts, likes, media, and cursors. Use [Community Search](/api-reference/x/community-search) for keyword matches inside the reviewed Community ID. Read recent conversations before drafting a reply or media post. Membership never authorizes publishing. Approve separate Create Tweet actions for members posting after joining. Keep the reviewed rules with each approval. Avoid repetitive promotions and unwanted invitations. Moderators can remove accounts that violate local rules. ## Resolve Membership Intent Use a numeric Community ID. Record its connected account, membership mode, and approval. Keep enough evidence to explain every membership request. | Approval evidence | Store | Purpose | | ------------------ | -------------------------------------------------------- | ---------------------------------------------------- | | Intended Community | Numeric ID, display name, and direct URL | Prevent membership in a similarly named Community | | Connected account | Username, stable user ID, and connection status | Bind consent to one Twitter account | | Membership review | Mode, rules, and moderator status | Explain an open, pending, or rejected request | | API action | Idempotency key, action ID, status URL, and final status | Prevent duplicate writes and preserve recovery state | ## How to Join Twitter Community Membership Safely The steps below explain how to join Twitter Community membership through Xquik. 1. Review the numeric Community ID and connected account. 2. Create one pair-specific `Idempotency-Key`. 3. Submit `POST /x/communities/{id}/join` once. 4. Poll the returned action to a terminal result. 5. Verify the account through Community Members. 6. Store the action, approval, membership mode, and result. An HTTP `202` confirms processing, not membership. If a timeout occurs, keep polling the existing action. A changed account or Community needs new approval. Store the request hash, action ID, status URL, and final membership result. Keep the same `Idempotency-Key` only when every input remains identical. A changed account, Community ID, or approval requires a new key. ## Join Communities in Controlled Batches Assign one `Idempotency-Key` per approved pair. Process retries serially. Record each result, action ID, and approval in one durable ledger row. Pause only the affected account after errors. ## Verify Visible Membership After a terminal result, call [Community Members](/api-reference/x/community-members). Follow every cursor. Store the Community ID, action status, matched username, and stable user ID. Never resubmit after checking only one page. Anyone with the direct Community URL can see its member list. ## Explain Community Post Visibility Community posts are not private group messages. They can appear on Community pages, profiles, timelines, and search. Anyone on X with the direct Community URL can see the Community's member list. Joining never publishes a post. Use [Create Tweet](/api-reference/x-write/create-tweet) after separate approval. Community membership is not a private audience. A Community post can appear on the Community page, its author's profile, timelines, and search. Review that visibility before approving a post. ## Answer Common Twitter Community Questions ### How to Join a Twitter Community with Xquik? Review Community Info. Submit one connected account and numeric ID. Poll the action, then verify Community Members. ### Can I Join an Invitation-Only Twitter Community? For an invite-only Community, X still requires an invitation. Confirm you are logged in to your account on X. This endpoint cannot accept invitations. Preserve any `422` rejection. ### What Happens When You Join a Twitter Community? X adds the membership after approval. Members can reply after X confirms membership. They can connect, share posts, and follow the Community's rules. ### Can People See the Communities You Join on Twitter? Direct Community URLs expose member lists. Posts can also appear publicly. ### How Do I Join a Community on a Phone? Open a shared Community URL. Review its rules and membership mode. Use X for manual membership or Xquik for server workflows. ### Do Twitter Communities Still Exist? Yes. X controls availability, search, membership modes, and moderation. ### Do I Need X Premium to Join a Community? The request has no Premium field. X decides account eligibility. ### Why Can't I Join a Twitter Community? Check the public account, numeric ID, connection, mode, and invitation. Check whether the account and moderators block each other. Read `terminal`, `retryable`, and `safeToRetry` before retrying. ### How Do I Find Active Twitter Communities by Interest? Search the Communities page by topic or creator after joining once. A first membership needs a shared URL. Community Search cannot find Community directories. ### What Are the Benefits of Joining a Twitter Community? Membership opens one topic-focused discussion space. Members can read its timeline and participate under its rules. This endpoint cannot promise followers, engagement, leads, or sales. ### How Should a Brand Participate After Joining? Read recent Community posts first. Match the topic and follow moderator rules. Approve each reply, post, and media upload separately. Avoid repetitive promotions and unwanted invitations. ### Is Community Notes the Same as X Communities? No. Community Notes adds context to posts. X Communities organize memberships and discussions. Use X to join Community Notes. ### Can This API Create, Moderate, or Grow a Community? No. Use [Create Community](/api-reference/x-write/create-community) for creation. Join Community cannot moderate, invite, or grow members. ## Recover From Join Failures Fix input after `400`. Replace credentials after `401`. Restore credits after `402`. Reconnect the account after `403`. Check both IDs after `404`. Reuse the same key only for identical input after `409`. Preserve `422` rejections. Honor `Retry-After` after `429`. Inspect `safeToRetry` after `500` or `503`. If a connection drops after submission, poll the existing action. Never create a replacement action from network uncertainty. Preserve the original action after `409`. Store X membership rejections after `422`. Wait for `Retry-After` after `429`. Store the action ID and final membership result. Never infer posting permission from membership. ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended write. Reuse it only when every request field remains unchanged. Must be `application/json`. ## Path parameters The ID of the community to join. ## Body The connected X account to join the community with. Must be a username you have connected to your Xquik account. ## Response Connect the requested account, then submit a newly approved write. ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # How to Leave a Community on Twitter via API Source: https://docs.xquik.com/api-reference/x-write/leave-community DELETE /x/communities/{id}/join Learn how to leave a Twitter Community by ID with one connected account. Track every API response, keep existing posts, and verify membership removal safely. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "leave_community", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "leave_community", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
## How to Leave a Community on Twitter via API Call `DELETE /x/communities/{id}/join` to remove one connected account's membership. Other members keep access to the Community. Xquik tracks the departure as a write action. This route does not delete the Community. It does not delete the connected Twitter account. It also does not remove Community posts or replies. Use [Delete Community](/api-reference/x-write/delete-community) only for an owned Community that should close. Use [Delete Tweet](/api-reference/x-write/delete-tweet) for an owned post that should disappear. **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/x/communities/1893726451023847424/join \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: community-leave-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch( "https://xquik.com/api/v1/x/communities/1893726451023847424/join", { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "community-leave-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxhandle", }), } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.delete( "https://xquik.com/api/v1/x/communities/1893726451023847424/join", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "community-leave-1895432178065391234", }, json={ "account": "myxhandle", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "myxhandle", }) req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/x/communities/1893726451023847424/join", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "community-leave-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## How to Leave a Twitter Community with Xquik This section explains how to leave a Twitter Community through Xquik. It also shows how to leave Twitter Community membership while keeping the group active. Resolve and review the numeric Community ID. Call [Community Info](/api-reference/x/community-info) to confirm its name, rules, creator, and description. Show the selected connected account beside those details. Keep the Community ID separate from the connected account ID. Check [Community Members](/api-reference/x/community-members) when current membership needs proof. Follow every cursor before declaring the account absent. One partial page proves nothing. Record these values before the write: * Numeric Community ID and verified name. * Connected username and stable account ID. * Departure reason and approval reference. * New idempotency key for this request. * Pre-departure roster time and cursor coverage. Never reuse the earlier join key. Joining and leaving are separate actions. ## Archive Community Posts Before Leaving Leaving does not erase earlier Community posts or replies. X says those posts continue to exist after a member leaves. They can remain visible on the Community page, profiles, timelines, and search. Archive required posts before removing membership. Follow every [Community Tweets](/api-reference/x/community-tweets) cursor. Save Tweet IDs, authors, text, timestamps, media URLs, replies, reposts, likes, and cursors. Use [Community Search](/api-reference/x/community-search) for keyword matches inside the known Community. That route cannot archive every post unless the query covers them. Use the unfiltered timeline for a complete export. No request field sends a notification. Notify members through a separate approved workflow when a handoff requires notice. Never claim that this API sends a farewell post, direct message, or moderator alert. ## How to Leave Twitter Community Membership Safely Create one idempotency key for this departure. Send the Community ID in the path. Send the connected username through `account`. Use this sequence: 1. Verify the Community and connected account. 2. Archive required Community posts and member evidence. 3. Approve one membership-removal request. 4. Submit `DELETE /x/communities/{id}/join` once. 5. Store the returned action ID immediately. 6. Poll until the action reaches a terminal result. 7. Verify the account no longer appears as a member. A `202` response confirms accepted processing. It does not confirm departure. Do not create another key during a timeout. Inspect the existing action first. ## Verify the Twitter Community Departure Read [Community Members](/api-reference/x/community-members) after terminal success. Follow every cursor required for the account search. Store the final cursor, lookup time, username, and account ID. Keep the write receipt separate from later roster snapshots. The receipt shows the requested action. The roster shows membership observed later. If the account remains, refresh the complete roster once. Pair that result with the returned action ID. Do not submit parallel departures with different keys. | Membership task | Correct route | Expected result | | --------------------- | --------------------------------- | ---------------------------------------------- | | Confirm the Community | `GET /x/communities/{id}/info` | Review its name, rules, creator, and policies. | | Confirm membership | `GET /x/communities/{id}/members` | Find the account in a complete roster. | | Leave the Community | `DELETE /x/communities/{id}/join` | Remove one connected account's membership. | | Rejoin later | `POST /x/communities/{id}/join` | Submit a new membership action. | | Delete the Community | `DELETE /x/communities/{id}` | Remove the entire owned Community. | ## Leave a Twitter Community on PC or Phone For a manual departure, X says to open the Community page. Select the Joined button, then choose Leave the community. X currently documents that flow for its Community interface. Use the API for software workflows. The request stays the same across server, desktop, iOS, and Android clients. Do not automate screen labels when the REST route fits the workflow. Read the official [X Communities guide](https://help.x.com/en/using-x/communities) for current interface behavior. X can change its buttons without changing Xquik's documented request. ## Answer Common Community Departure Questions ### Do My Community Posts Disappear After I Leave? No. X says Community posts and replies remain after their author leaves. Use Delete Tweet for an owned post that should be removed. ### Can I Rejoin the Community Later? Yes, when X permits membership. X may accept the original invitation during a later membership request. Restricted Communities may need moderator approval. Rejoining needs another approval and idempotency key. ### Can This API Cancel a Pending Join Request? No. This route removes existing membership. The canonical API has no cancel-pending-request field or separate cancellation endpoint. ### Does Leaving Delete the Community? No. Other members, moderators, rules, and Community posts remain. Use Delete Community only to close an owned group. ### Is a Twitter Group Chat the Same as a Community? No. Leaving a Community keeps every group direct message unchanged. ### Can an Admin Transfer Ownership Before Leaving? This request has no ownership-transfer field. Finish any available transfer in X. Verify the new admin before leaving. ### What Are the Privacy Effects of Leaving? The departing account stops appearing as a current member after success. Earlier posts can remain public. Saved exports and third-party copies also remain outside this membership action. ### Is Community Notes the Same as X Communities? No. Community Notes adds context to eligible posts. Use its separate workflow for Community Notes participation. ### Do Twitter Communities Still Exist? Yes. X still publishes current Communities guidance. X controls interface availability, membership modes, invitations, moderation, and search. ## Recover From Leave Community Failures Fix malformed IDs or bodies after `400`. Replace invalid credentials after `401`. Restore credits after `402`. Reconnect the selected account after `403`. Check both the account and Community after `404`. A missing account differs from a missing Community. Preserve that distinction in operator messages. For `409`, compare the saved account, Community ID, and request body. Keep the key only for identical inputs. Changed inputs need fresh approval. Preserve `422` rejection details. Do not delete the Community as a fallback. After `429`, wait for `Retry-After`. Keep the approved inputs unchanged. After `500` or `503`, inspect `safeToRetry` and the existing lifecycle. Never infer failure from a dropped connection. Verification prevents duplicate membership writes. ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters The ID of the community to leave. ## Body The connected X account to remove from the community. Must be a username you have connected to your Xquik account. ## Response Connect the requested account, then submit a newly approved write. ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Like API: Like a Tweet by ID With Status Source: https://docs.xquik.com/api-reference/x-write/like POST /x/tweets/{id}/like Use the Twitter Like API to like one tweet by ID. Store the connected account, durable action, terminal result, billed credits, and safe retry decision. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "like", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "like", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "x_rate_limited", "message": "Rate limited by X. Wait before retrying." } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Use this Twitter Like API to like 1 tweet by ID. This Twitter API like Tweet request uses 1 connected X account. Save user approval before sending the request. Create a unique `Idempotency-Key` for the intended like. Store the durable write action and poll `statusUrl` when needed. Confirm the returned target matches the requested Tweet ID. X documents its separate [Like Post endpoint](https://docs.x.com/x-api/users/like-post). X says not to auto-like or bulk-like Tweets in its [Developer Guidelines](https://docs.x.com/developer-guidelines). Keep every like user-initiated. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/tweets/1895432178065391234/like \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: like-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "elonmusk" }' | jq '{ status, terminal, safe_to_retry: .safeToRetry, write_action_id: .id, request_hash: .request.hash, requested_tweet_id: "1895432178065391234", confirmed_tweet_id: (.result.id // .tweetId // .targetId), charged_credits: .billing.chargedCredits, poll_path: (if .terminal then null else .statusUrl end) }' ``` ```javascript Node.js theme={null} const tweetId = "1895432178065391234"; const response = await fetch(`https://xquik.com/api/v1/x/tweets/${tweetId}/like`, { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "like-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "elonmusk", }), }); const result = await response.json(); if (!response.ok) { throw new Error(JSON.stringify(result)); } const likeRecord = { status: result.status, terminal: result.terminal, safe_to_retry: result.safeToRetry, write_action_id: result.id, request_hash: result.request.hash, requested_tweet_id: tweetId, confirmed_tweet_id: result.result?.id ?? result.tweetId ?? result.targetId ?? null, account: result.account, charged_credits: result.billing.chargedCredits, poll_path: result.terminal ? null : result.statusUrl, }; process.stdout.write(`${JSON.stringify(likeRecord)}\n`); ``` ```python Python theme={null} import json import requests tweet_id = "1895432178065391234" response = requests.post( f"https://xquik.com/api/v1/x/tweets/{tweet_id}/like", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "like-1895432178065391234", }, json={ "account": "elonmusk", }, ) result = response.json() response.raise_for_status() like_record = { "status": result["status"], "terminal": result["terminal"], "safe_to_retry": result["safeToRetry"], "write_action_id": result["id"], "request_hash": result["request"]["hash"], "requested_tweet_id": tweet_id, "confirmed_tweet_id": ( (result.get("result") or {}).get("id") or result.get("tweetId") or result.get("targetId") ), "account": result["account"], "charged_credits": result["billing"]["chargedCredits"], "poll_path": None if result["terminal"] else result["statusUrl"], } print(json.dumps(like_record)) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" ) type LikeTweetResponse struct { ID string `json:"id"` Status string `json:"status"` Terminal bool `json:"terminal"` SafeToRetry bool `json:"safeToRetry"` StatusURL string `json:"statusUrl"` TargetID *string `json:"targetId"` TweetID *string `json:"tweetId"` Request struct { Hash string `json:"hash"` } `json:"request"` Account map[string]any `json:"account"` Billing struct { ChargedCredits string `json:"chargedCredits"` } `json:"billing"` Result *struct { ID string `json:"id"` } `json:"result"` } func main() { tweetID := "1895432178065391234" body, err := json.Marshal(map[string]string{ "account": "elonmusk", }) if err != nil { panic(err) } req, err := http.NewRequest( "POST", "https://xquik.com/api/v1/x/tweets/"+tweetID+"/like", bytes.NewReader(body), ) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "like-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode >= 400 { responseBody, _ := io.ReadAll(resp.Body) panic(string(responseBody)) } var result LikeTweetResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { panic(err) } var confirmedTweetID any if result.Result != nil && result.Result.ID != "" { confirmedTweetID = result.Result.ID } else if result.TweetID != nil { confirmedTweetID = *result.TweetID } else if result.TargetID != nil { confirmedTweetID = *result.TargetID } var pollPath any if !result.Terminal { pollPath = result.StatusURL } likeRecord := map[string]any{ "status": result.Status, "terminal": result.Terminal, "safe_to_retry": result.SafeToRetry, "write_action_id": result.ID, "request_hash": result.Request.Hash, "requested_tweet_id": tweetID, "confirmed_tweet_id": confirmedTweetID, "account": result.Account, "charged_credits": result.Billing.ChargedCredits, "poll_path": pollPath, } encoded, err := json.Marshal(likeRecord) if err != nil { panic(err) } fmt.Println(string(encoded)) } ``` ## Like One Tweet by ID Put the exact Tweet ID in the request path. Put the connected account username or ID in the body. That account performs the like. Copy Tweet IDs as strings. JavaScript numbers cannot safely represent every X identifier. Keep the selected Tweet ID beside the approving user. This endpoint accepts 1 Tweet ID per request. It does not fetch Tweets, create reposts, or publish replies. Use [Get Tweet](/api-reference/x/get-tweet) before showing an approval screen. ## Keep Every Like User-Initiated Require a clear user choice before each like. Show the tweet text, author, and Tweet ID during approval. Store who approved the like and when. Do not build auto-like, bulk-like, or purchased-like workflows. Do not select scraped tweets and like them without user approval. Only send the like that your user picked. A review queue can prepare candidates without performing actions. Your user must choose the exact tweet before this endpoint runs. Keep the approval reference in the action ledger. ## Authenticate the Connected X Account Send an Xquik API key or OAuth 2.1 bearer token. Then identify the connected X account with `account`. The request never accepts an X password or session cookie. Keep Xquik credentials in server-side code. Do not expose them in mobile apps or browser bundles. Reconnect that X account when you receive `account_needs_reauth`. The official X endpoint uses a user access token. Its authentication differs from the Xquik contract. Read X's [Like Post guide](https://docs.x.com/x-api/users/like-post) for that endpoint. ## Save a Durable Like Receipt Never treat a transport response as sufficient proof. Save the action first. Then update your like record. Keep the request and response identities together. | Like action column | Source | Engagement rule | | -------------------- | ---------------------- | -------------------------------------------------- | | `acting_account` | Request `account` | Keep the connected X account with the engagement. | | `tweet_id` | Path `id` | Use the stable Tweet ID selected by review. | | `approval_reference` | Workflow value | Store the user's approval ID with this like. | | `idempotency_key` | Request header | Generate one key for this account and Tweet ID. | | `write_action_id` | Response `id` | Store the durable action before polling. | | `status` | Response `status` | Keep queued, completed, and failed likes distinct. | | `terminal` | Response `terminal` | Mark the like complete only when `true`. | | `safe_to_retry` | Response `safeToRetry` | Retry when this value is `true`. | | `requested_at` | Integration timestamp | Record when the user approved this like. | Persist these response fields: * Store `id` as the durable write action ID. * Store `request.hash` for exact request matching. * Store `account` for the connected X account. * Store `targetId` or `result.id` for the liked Tweet ID. * Store `status`, `terminal`, and `statusUrl` for polling. * Store `billing.chargedCredits` for the settled charge. * Store `safeToRetry` before considering another request. ## Poll Pending Likes and Verify Completion A `200` response contains a terminal action. A `202` response means the like remains active. Poll `statusUrl` until `terminal` becomes `true`. Do not submit another like while the first action remains active. After success, confirm the result matches the requested Tweet ID. Preserve an already-liked terminal result as successful convergence. Use [User Likes](/api-reference/x/user-likes) only when current read proof matters. Store the read time separately from the action time. Read visibility can limit independent verification. | Like outcome | Evidence | Ledger update | | ----------------- | ------------------------- | -------------------------------------------------------- | | Queued | `terminal: false` | Keep the row pending and poll `statusUrl`. | | Completed | Terminal success | Record the completed like for this account and Tweet ID. | | Already liked | Converged terminal result | Complete without submitting another like. | | Retryable failure | `safeToRetry: true` | Keep the receipt. Follow its retry fields. | | Final failure | Terminal failure | Store the error and keep the ledger unchanged. | ## Prevent Duplicate Tweet Likes Create 1 idempotency key for the intended account and Tweet ID. Reuse that key only after an interrupted response. Never reuse it for a different account or tweet. An HTTP `409` means the key already protects another payload. Keep the original action and inspect its request hash. Create a new key only for a new user-approved intent. Idempotency prevents duplicate submissions inside the Xquik workflow. Still check approval, terminal status, and the acting account. ## Handle Twitter Like API Errors Never infer failure from a client timeout. Poll a returned action before sending another request. Follow `safeToRetry` for ambiguous write outcomes. * For `400`, correct the Tweet ID or account value. * For `401`, replace invalid Xquik authentication. * For `402`, add credits before another like. * For `403`, reconnect the selected X account. * For `409`, keep the original action and payload. * For `422`, review the X rejection before retrying. * For `429`, wait for `Retry-After`. * For `500` or `503`, follow `safeToRetry` and `terminal`. X publishes separate limits for its [Like Post endpoint](https://docs.x.com/x-api/fundamentals/rate-limits). Those limits do not replace Xquik's response headers. Never increase throughput by creating a bulk-like queue. ## Choose Like, Unlike, or Read Likes Use this route only to add a like from one connected account. Use [Unlike Tweet](/api-reference/x-write/unlike) to reverse that account's like. Use read endpoints when no engagement action is needed. | Engagement intent | API route | Result | | -------------------------------- | ------------------------------- | ------------------------------------------- | | Like one tweet | `POST /x/tweets/{id}/like` | Adds the connected account's like. | | Unlike one tweet | `DELETE /x/tweets/{id}/like` | Removes the connected account's like. | | Read a user's liked tweets | `GET /x/users/{id}/likes` | Returns that user's available liked tweets. | | Read accounts that liked a tweet | `GET /x/tweets/{id}/favoriters` | Returns available liking accounts. | | Repost one tweet | `POST /x/tweets/{id}/retweet` | Creates a separate repost action. | Liking does not create a repost, reply, bookmark, or follow. Choose each action explicitly during approval. ## Twitter Like API Questions ### Can an API like a tweet by ID? Yes. Send the Tweet ID in this route's path. Send the connected acting account in the JSON body. Keep the like user-initiated. ### Does this endpoint bulk-like tweets? No. It accepts 1 Tweet ID per request. X prohibits bulk-like and auto-like behavior. Do not loop through candidates without individual user approval. ### How does Twitter Like API authentication work? Authenticate to Xquik with an API key or OAuth 2.1 bearer token. Select a previously connected X account with `account`. Never send an X password. ### How should a Twitter API like Tweet retry work? Store the durable action first. Poll while `terminal` is `false`. Retry only when `safeToRetry` is `true`. ### Can the same API like, repost, and reply? Xquik documents separate routes for each action. Use [Retweet](/api-reference/x-write/retweet) for a repost. Use [Create Tweet](/api-reference/x-write/create-tweet) for a reply. ### Which libraries can like a tweet? Any HTTP client can call this REST endpoint. The examples cover cURL, Node.js, Python, and Go. Keep credentials in server-side code. ## Headers Send your Xquik API key in this header. Alternatively, send an OAuth 2.1 bearer token. Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters The ID of the tweet to like. ## Body X username or account ID identifying which connected X account will like the tweet. The `@` prefix is automatically stripped if included. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # How Do You Remove a Twitter Follower? Xquik API Source: https://docs.xquik.com/api-reference/x-write/remove-follower POST /x/users/{id}/remove-follower Remove one unwanted Twitter follower by X user ID. Approve the connected account, poll the durable action, verify the inbound relationship, and handle errors. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "remove_follower", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "remove_follower", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
## How Do You Remove a Twitter Follower? Use this endpoint to remove 1 follower from a connected X account. Put the follower's numeric X user ID in the path. Name the connected account in the JSON body. Then approve, poll, and verify the inbound relationship change. X also documents [manual follower removal](https://help.x.com/en/using-x/following-faqs). Its help page explains that a removed account may follow again. **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit **Action-specific rate limit:** Remove Follower shares **20 requests per minute** and **400 per day** with follow actions. The POST tier also allows **120 requests per minute**. A breached limit returns `429 Too Many Requests`. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/users/44196397/remove-follower \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: remove-follower-myxaccount-44196397-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxaccount" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/users/44196397/remove-follower", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "remove-follower-myxaccount-44196397-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxaccount", }), }); const removalReceipt = await response.json(); if (!response.ok) { throw new Error(JSON.stringify(removalReceipt)); } ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/x/users/44196397/remove-follower", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "remove-follower-myxaccount-44196397-1895432178065391234", }, json={ "account": "myxaccount", }, ) removal_receipt = response.json() response.raise_for_status() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "myxaccount", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/users/44196397/remove-follower", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "remove-follower-myxaccount-44196397-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var removalReceipt map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&removalReceipt); err != nil { panic(err) } fmt.Println(removalReceipt) } ``` ## Resolve the Follower and Connected Account Read [Followers](/api-reference/x/followers) before choosing a target. Resolve the current username to a stable numeric X user ID. Usernames and profile names can change. The numeric ID keeps the moderation record tied to one account. Show the follower ID, username, profile name, and acting account. Also show the moderation reason before approval. The path `{id}` identifies the follower you will remove. The body field `account` identifies whose follower list will change. Never infer spam from a username, avatar, or missing profile field. Choose the unwanted follower with your approved moderation rule. This helps you identify and remove the right account. Store the reviewer, reason, and evidence outside this public request. ## Remove Followers on Twitter Safely Create 1 idempotency key for each intended removal. Reuse that key only after an interrupted network request. Never reuse it for another follower or connected account. This route accepts 1 follower ID per request. It provides no bulk body for mass follower removal. Queue approved follower IDs as separate write actions instead. Each row needs its own reason, approval, and idempotency key. A social media team can review the queue before dispatch. Use the queue to manage your Twitter account safely. Do not let a temporary failure erase successful removals. Store each returned action ID beside its target follower ID. ## Poll and Verify the Removal Store `id`, `request.hash`, `billing`, and `statusUrl` immediately. An HTTP `200` response represents a terminal write action. An HTTP `202` response represents an active write action. Poll the same `statusUrl` while `terminal` remains `false`. Never submit another removal just because the first action is active. A second request could add a duplicate job. Keep the original idempotency key for an exact network replay. Read [Followers](/api-reference/x/followers) after the action becomes terminal. You can also call [Check Follower](/api-reference/x/check-follower). Set the removed account as the source relationship participant. Set the connected account as the target participant. Confirm the inbound follower relationship is absent. ## Choose Remove Follower, Unfollow, or Block Remove Follower ends one inbound relationship. The selected account stops following your connected X account. It does not make your connected account unfollow the selected account. Use [Unfollow](/api-reference/x-write/unfollow) for that outbound relationship. Keep both actions separate in every moderation workflow. The accounts do not need a mutual follow relationship. Follower removal is also different from blocking. A removed follower can follow your public account again. They can ask to follow your protected account again. Block the account through X when it must not follow again. This endpoint lets you remove a follower without blocking them. ## Review Bots, Private Accounts, and Bulk Queues This endpoint does not detect bots, spam, or inactive accounts. Check profiles and follower relationships before you approve a removal. Never treat a protected profile as proof of abusive behavior. Private and public accounts use the same Xquik request shape. However, X controls whether the removed account can follow again. Do not promise a permanent audience restriction from this write. Store the terminal result, then verify the live follower relationship. For multiple targets, process one approved queue row at a time. Throttle the queue to every documented rate limit. ## Handle Remove Follower API Errors Fix the follower ID or connected account after `400`. Replace Xquik authentication after `401`. Add credits after `402`; reconnect the account after `403`. Keep the original action after an idempotency conflict at `409`. Fix rejected input after `422`. Honor `Retry-After` after `429`. After `500` or `503`, inspect `safeToRetry` before retrying. ## Twitter Follower Removal Questions ### How to Remove Followers on Twitter with an API? Resolve each follower to a numeric ID. Send one approved request per follower, then poll every action. ### How Do You Remove a Follower from Twitter Without Blocking? Call this endpoint for the inbound relationship. The removed account may still follow the connected account again. ### Can I Remove All Followers or Bot Followers at Once? No batch body is available. Review suspected bots first, then queue one approved target per request. ### Does X Notify a Removed Follower? No response field tells you whether X sent a notification. Your final check should cover only the follower relationship. ### Can I Unfollow Accounts with This Endpoint? No. Use Unfollow when the connected account follows another account. Use Remove Follower when someone follows you. ### Does This Work for Mobile, Desktop, or Private Accounts? The REST request works from any HTTP client. It does not depend on X's mobile or desktop interface. ### How Do I Get an API Key? Create an Xquik API key in the dashboard. You can also send an OAuth 2.1 bearer token. ## Headers Send your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters The X user ID of the follower to remove. ## Body X username or account ID of your connected account whose follower list should be updated. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Retweet API: Repost One Tweet by ID Source: https://docs.xquik.com/api-reference/x-write/retweet POST /x/tweets/{id}/retweet Repost a tweet by ID with the Twitter Retweet API. Approve the account, use an idempotency key, poll the action, then store billed credits and final result. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "retweet", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "retweet", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "x_rate_limited", "message": "Rate limited by X. Wait before retrying." } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Use this Twitter Retweet API to repost 1 tweet by ID. Each Twitter API retweet request names 1 connected X account. Review the source tweet before sending the write. Then store the action and poll `statusUrl` until `terminal` is `true`. Compare X's separate [Repost Post endpoint](https://docs.x.com/x-api/users/repost-post). ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/tweets/1895432178065391234/retweet \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: retweet-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "elonmusk" }' | jq ``` ```javascript Node.js theme={null} const tweetId = "1895432178065391234"; const response = await fetch(`https://xquik.com/api/v1/x/tweets/${tweetId}/retweet`, { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "retweet-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "elonmusk", }), }); const repostReceipt = await response.json(); if (!response.ok) { throw new Error(JSON.stringify(repostReceipt)); } ``` ```python Python theme={null} import requests tweet_id = "1895432178065391234" response = requests.post( f"https://xquik.com/api/v1/x/tweets/{tweet_id}/retweet", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "retweet-1895432178065391234", }, json={ "account": "elonmusk", }, ) repost_receipt = response.json() response.raise_for_status() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { tweetID := "1895432178065391234" body, _ := json.Marshal(map[string]interface{}{ "account": "elonmusk", }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/tweets/"+tweetID+"/retweet", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "retweet-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var repostReceipt map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&repostReceipt); err != nil { panic(err) } fmt.Println(repostReceipt) } ``` ## Authenticate and Approve the Reposting Account Send an Xquik API key or OAuth 2.1 bearer token. Name 1 connected X account in the request body. Connect that account before scheduling or approving a repost. Never send an X password or session cookie. Review the source tweet immediately before approval. Show its author, text, media, Tweet ID, and current availability. Also show the X account that will publish the repost. Use this review to avoid sharing from the wrong account. Store these approval fields with the request: | Approval field | Source | Purpose | | --------------- | ----------------- | ------------------------------------------- | | Acting account | Request `account` | Identify the account publishing the repost. | | Source Tweet ID | Path `id` | Preserve the approved tweet identifier. | | Source author | Tweet lookup | Keep original attribution in the review. | | Approval time | Workflow clock | Record when the user approved distribution. | | Approval owner | Workflow identity | Record who approved the repost. | | Idempotency key | Request header | Bind retries to this exact decision. | ## Publish One Repost by Tweet ID Resolve the Tweet ID before approval. Show the source tweet, author, media, and acting account. This endpoint preserves the original tweet and attribution. Assign a unique idempotency key to that repost decision. Reuse it only after an interrupted response. Store the action ID with the Tweet ID and acting account. Repost workflows often include: * Amplifying an approved announcement from a partner account. * Reposting a customer reply selected by a support team. * Sharing a monitored tweet after a moderation checkpoint. * Preventing two workers from reposting the same tweet. An accepted action may not appear on the account timeline yet. Poll the lifecycle state while the action remains active. | Repost action column | Source | Distribution rule | | -------------------- | --------------------- | -------------------------------------------------------- | | `acting_account` | Request `account` | Keep the connected account with the repost decision. | | `tweet_id` | Path `id` | Use the exact source Tweet ID approved for distribution. | | `source_author_id` | Review context | Preserve the original tweet author when available. | | `approval_reference` | Workflow value | Link the repost to its campaign or moderation decision. | | `idempotency_key` | Request header | Generate one key for this account and Tweet ID. | | `write_action_id` | Response `id` | Store the durable repost action before polling. | | `status` | Response `status` | Keep queued, completed, and failed reposts distinct. | | `terminal` | Response `terminal` | Mark distribution complete only when `true`. | | Request time | Integration timestamp | Save when the repost request starts. | | Repost outcome | Evidence | Campaign action | | ---------------- | ------------------------- | ------------------------------------------------------------- | | Queued | `terminal: false` | Poll `statusUrl`; do not count the repost yet. | | Completed | Terminal success | Record the account, source Tweet ID, and completion time. | | Already reposted | Converged terminal result | Complete without another repost request. | | Retry allowed | `safeToRetry: true` | Keep the receipt; follow `nextAction`. | | Final failure | Terminal failure | Store the error and exclude the repost from completed totals. | ## Choose a Repost or New Tweet Use a repost to share an existing tweet unchanged. The request adds no comment, caption, or reply text. Use [Create Tweet](/api-reference/x-write/create-tweet) for original posts and replies. A reply needs `reply_to_tweet_id`. | Publishing intent | API route | Required content | | --------------------------------- | ------------------------------- | ------------------------------------- | | Share an existing tweet unchanged | `POST /x/tweets/{id}/retweet` | Source tweet ID and connected account | | Publish original text | `POST /x/tweets` | New tweet text and connected account | | Reply to a tweet | `POST /x/tweets` | Reply text and `reply_to_tweet_id` | | Remove a repost | `DELETE /x/tweets/{id}/retweet` | Source tweet ID and connected account | Show the source tweet during approval. Preserve its author, text, media, and Tweet ID. Never replace that ID with a search-result position. ## Reconcile Retweet Automation Store one receipt per connected account and source tweet. This key prevents two workers from counting the same repost as separate campaign results. Keep queued, completed, already-reposted, and failed outcomes distinct. Mark an already-reposted outcome complete. It does not create a second repost. Use [Unretweet](/api-reference/x-write/unretweet) for an approved rollback. The rollback needs a new idempotency key. Preserve both action IDs so the campaign record shows distribution and later removal. ## Schedule Retweets Without Duplicate Posts This endpoint starts the repost action when it receives the approved request. Use your scheduler when a repost must run later. Store the Tweet ID, account, time zone, and approval together. Recheck the source tweet before dispatch. Create the idempotency key when the schedule becomes immutable. Reuse that key after a network interruption. Never reuse it for another account, Tweet ID, or scheduled time. The endpoint accepts 1 Tweet ID per request. It has no bulk repost body. Queue several approved tweets as separate jobs. Store each action before starting the next job. After `429`, honor `Retry-After`. Do not rotate connected accounts to bypass a limit. Delay the existing job instead of creating a duplicate. ## Verify Repost Completion and Attribution Store `id` as the durable write action ID. Store `request.hash` with the submitted account and Tweet ID. Keep `status`, `terminal`, `statusUrl`, and `safeToRetry` together. Store `billing.chargedCredits` after billing settles. Treat `202` as active work, not a completed repost. Poll the same `statusUrl` until `terminal` becomes `true`. Never send another repost while the action remains active. After success, compare the returned target with the approved Tweet ID. Mark an already-reposted outcome complete. It does not create another timeline entry. The repost keeps the source tweet's original attribution. Use [Retweeters](/api-reference/x/retweeters) for available account-level evidence. Timeline visibility can lag behind a completed write receipt. Keep read time separate from repost completion time. ## Handle Twitter Retweet API Errors Keep every error beside the request hash and action ID. Never replace the approved Tweet ID as an automatic fallback. * `400`: Correct the Tweet ID or account value. * `401`: Replace invalid Xquik authentication. * `402`: Add credits before another repost request. * `403`: Reconnect the selected X account. * `409`: Keep the action protected by that idempotency key. * `422`: Review X's rejection before another attempt. * `429`: Honor `Retry-After` before polling or retrying. * `500`: Store the failure; contact support if it persists. * `503`: Check `safeToRetry` before retrying. A client timeout does not prove the repost failed. Check any returned action before sending another write. Use `nextAction` to choose the next step. ## Apply Repost Bot and Campaign Controls Require approval for the exact account and source tweet. Use campaign allowlists for authors, topics, or Tweet IDs. Set a per-campaign repost limit before dispatch begins. Keep separate limits for each connected account. Lock the approved tweet list before processing. Record skipped, active, completed, and failed jobs separately. Never count an accepted action as completed. Inspect unavailable tweets instead of substituting new content. Keep deleted or restricted source tweets as distinct outcomes. Do not convert a failed repost into an original tweet automatically. Store the approval, request, action, billing, and final result. These records support audits without exposing account credentials. ## Twitter Retweet API Questions ### How Do I Automate a Retweet Through an API? Approve the Tweet ID and connected account. Send this request, then poll the durable action until terminal. ### How Do I Authenticate a Twitter API Retweet? Send an Xquik API key or OAuth 2.1 bearer token. Never send an X password or session cookie. ### Can I Schedule a Retweet? Call this endpoint from your scheduler after approval. The endpoint stores no future schedule. ### What Is the Retweet API Rate Limit? After `429`, honor `Retry-After` before another request. X publishes separate [Repost rate limits](https://docs.x.com/x-api/fundamentals/rate-limits). ### Is a Retweet the Same as a Quote Tweet? No. This route reposts the existing tweet unchanged. It cannot add a quote comment or new media. ### Can I Bulk Retweet Several Tweet IDs? No bulk request exists here. Approve and submit each Tweet ID separately. ### How Do I Track Retweet Activity? Poll this write action for completion. Use [Retweeters](/api-reference/x/retweeters) to read available reposting accounts. ### Why Can a Twitter Retweet API Request Fail? The account may need reconnection, credits, or a later retry. The source tweet may also be unavailable or rejected by X. ### Does a Repost Keep the Original Author? Yes. A repost references the source tweet and its author. This endpoint adds no replacement caption or attribution. ### Can I Undo an API Retweet? Yes. Call [Unretweet](/api-reference/x-write/unretweet) after new approval. Use a new idempotency key for that removal. ### Can a Bot Use This Retweet API? Yes. Keep credentials server-side and require clear account authorization. Apply approvals, limits, idempotency, and terminal polling to every job. ### Do I Need a Twitter Retweet SDK? No. Call this REST endpoint with any HTTP client. Use the cURL, Node.js, Python, or Go examples above. ## Headers Send your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters The ID of the tweet to retweet. ## Body X username or account ID identifying which connected X account will retweet. The `@` prefix is automatically stripped if included. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter DM API: Send Direct Messages with Media Source: https://docs.xquik.com/api-reference/x-write/send-dm POST /x/dm/{userId} Send text or one media attachment through the Twitter DM API. Authenticate the connected X account, poll the durable action, and handle every response. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "send_dm", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "send_dm", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**10 credits per send** · [Compare plans](https://xquik.com/#pricing) ## Send Direct Messages with the Twitter DM API Use this Twitter DM API for approved, one-to-one messages. The Twitter API DM route sends text or one uploaded media attachment. This Twitter API send DM workflow supports SDKs, support tools, and server jobs. Choose the connected X account and the recipient's numeric user ID. Use [Twitter profile lookup](/api-reference/x/twitter-profile-lookup) when you only know a username. Follow [X's Direct Message rules](https://docs.x.com/x-api/direct-messages/manage/integrate) for every message. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/dm/44196397 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: dm-44196397-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxaccount", "text": "Your requested account update is ready." }' | jq ``` ```javascript Node.js theme={null} const recipientUserId = "44196397"; const dmResponse = await fetch( `https://xquik.com/api/v1/x/dm/${recipientUserId}`, { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "dm-44196397-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxaccount", text: "Your requested account update is ready.", }), }, ); const dmAction = await dmResponse.json(); if (!dmResponse.ok) { throw new Error(dmAction.message); } process.stdout.write(`${JSON.stringify(dmAction)}\n`); ``` ## Authenticate and Select the Recipient Authenticate with an `x-api-key` header or an OAuth bearer token. The connected X account sends the message on the user's behalf. The path `userId` identifies the recipient, not the sender. Send only messages approved by your workflow. Honor recipient privacy, consent, and opt-out decisions. Read [DM history](/api-reference/x/dm-history) only for approved conversation context. ## Send Text or One Media Attachment Provide non-empty `text` for every direct message. Upload media first with [Upload Media](/api-reference/x-write/upload-media). Place its `mediaId` inside a one-item `media_ids` array. The upload costs another 10 credits. Empty or multi-item arrays return `400 invalid_input`. This route also rejects `reply_to_message_id`. It does not send group messages or accept public media URLs. ## Poll and Verify the Direct Message A `200` response contains a terminal write action. A `202` response requires polling through `statusUrl`. Wait for `terminal: true` before closing the send job. Store `writeActionId`, recipient ID, account, request hash, and `messageId`. If the network cuts out, use that same idempotency key. Never create another write while the first action remains nonterminal. The Xquik POST limit is 120 requests per minute. ## Handle Every Twitter DM API Response Fix request fields after `400`. Replace authentication after `401`, then add credits after `402`. Reconnect the X account after `403`. Keep the original request after a `409` idempotency conflict. `422 x_dm_not_allowed` means this sender cannot message that person. Try another approved sender or ask the person to allow DMs. Honor `Retry-After` after `429`. After `500` or `503`, inspect `safeToRetry` before retrying. ## Twitter DM API Questions ### Can I automate customer support direct messages? Yes. Queue approved replies and preserve the exact sent text. Avoid unsolicited bulk messaging and honor every opt-out. ### Can I send images or video through the API? Upload the file first, then send its single `mediaId`. This endpoint accepts one attachment per message. ### Does this endpoint retrieve message history or create webhooks? No. Use [Get DM History](/api-reference/x/dm-history) for prior messages. This send route does not configure incoming-DM notifications. ### Can third-party tools and generated SDKs call this route? Yes. Use the documented REST fields and authentication. Leave `reply_to_message_id` unset when a generated SDK exposes it. ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended send. Reuse it only for an exact network replay. Must be `application/json`. ## Path Parameters Numeric X user ID of the direct message recipient. ## Body X username or account ID of your connected sender account. Non-empty direct message text. Optional one-item array containing an uploaded media ID. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Unfollow API: Unfollow One X User by ID Source: https://docs.xquik.com/api-reference/x-write/unfollow DELETE /x/users/{id}/follow Use the Twitter Unfollow API to unfollow one X user by ID. Approve the connected account, poll the action, handle limits, and verify the relationship. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "unfollow", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "unfollow", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
## Unfollow One Twitter User by ID Use this Twitter unfollow API endpoint for 1 target user ID. Each Twitter API unfollow request names 1 connected X account. Verify the target user ID and acting account before approval. Compare X's separate [Unfollow User endpoint](https://docs.x.com/x-api/users/unfollow-user). **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit **Action-specific rate limit:** Unfollow shares the follow-action limits: **20 requests per minute** and **400 per day**. The DELETE tier also applies. A breached limit returns `429 Too Many Requests`. ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/x/users/44196397/follow \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: unfollow-44196397-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxaccount" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/users/44196397/follow", { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "unfollow-44196397-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxaccount", }), }); const unfollowReceipt = await response.json(); if (!response.ok) { throw new Error(JSON.stringify(unfollowReceipt)); } ``` ```python Python theme={null} import requests response = requests.delete( "https://xquik.com/api/v1/x/users/44196397/follow", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "unfollow-44196397-1895432178065391234", }, json={ "account": "myxaccount", }, ) unfollow_receipt = response.json() response.raise_for_status() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "myxaccount", }) req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/x/users/44196397/follow", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "unfollow-44196397-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var unfollowReceipt map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&unfollowReceipt); err != nil { panic(err) } fmt.Println(unfollowReceipt) } ``` ## Authenticate and Select the Relationship Send an Xquik API key or OAuth 2.1 bearer token. Never send an X password or session cookie. The path ID names the profile that the acting account follows. The body names the connected account that will stop following it. Use the stable numeric user ID as the relationship key. Keep the username and profile name as review labels. Show both profiles before a user approves the write action. The profile page and unfollow button are manual X controls. This endpoint supports an approved Twitter management workflow. ## Unfollow Twitter Accounts Safely Create a new idempotency key whenever the relationship changes. Never reuse the key from an earlier follow action. Reuse the unfollow key only after an interrupted network request. Store the returned action ID, request hash, and `statusUrl` immediately. Poll the same action while `terminal` remains `false`. Never submit a second write just because the first action is active. The endpoint accepts 1 target user ID per request. It exposes no mass unfollow body or bulk unfollow feature. Queue approved targets separately when you unfollow Twitter accounts in bulk. Each row needs its own target, reason, approval, and idempotency key. Separate rows isolate failures across multiple Twitter accounts. A failed row must not erase successful unfollow actions. Batch queues save time while preserving individual approvals. Review unfollowing accounts before sending any write. A social media team can stage approved rows for later processing. For accounts on Twitter, store each acting account and target ID separately. Respect X's terms of service and every returned rate limit. Unfollow people only after a clear user or workflow decision. ## Select Inactive Users Before Unfollowing This write endpoint does not discover inactive users or non-followers. Read [Following](/api-reference/x/following) before selecting cleanup targets. Define inactivity from an approved timestamp or business rule. Never infer inactivity from a missing profile or stale export. Store the selection rule with the target user ID. Review the acting account and target as separate fields. Then send 1 approved unfollow action for each relationship. ## Verify the Twitter Unfollow API Result Poll `statusUrl` until the durable action becomes terminal. Store the final status, billing fields, and request identifiers. Use [Check Follower](/api-reference/x/check-follower) when current proof matters. Check Following to confirm the target disappeared from the outbound list. No response field says whether X sent a notification. Do not claim whether X notified the target account. Verify the relationship instead of inferring client behavior. ## Choose Unfollow or Remove Follower Unfollow stops the connected account from following the target profile. [Remove Follower](/api-reference/x-write/remove-follower) ends one inbound relationship. Use Remove Follower when someone should stop following the connected account. Unretweet removes one repost but preserves every follow relationship. Delete Tweet removes one post but preserves follower relationships. Don't mix these write actions in one management tool flow. ## Handle Twitter API Unfollow Errors Fix the target user ID or acting account after `400`. Replace Xquik authentication after `401`; reconnect after `403`. Add credits after `402`; review rejected input after `422`. Keep the original action after an idempotency conflict at `409`. Honor `Retry-After` after `429`. After `500` or `503`, check `safeToRetry` before retrying. ## Twitter Unfollow API Questions ### How Do I Unfollow Someone on Twitter with an API? Approve the target user ID and connected account. Send this DELETE request, then poll the returned action until terminal. ### Can I Mass Unfollow or Schedule Unfollow Actions? Use your scheduler to queue 1 approved target per request. Throttle the queue to both documented limits above. ### Can I Unfollow Someone Without Them Knowing? Xquik cannot confirm whether X sends a notification. Confirm only the outbound relationship change. ### Do I Need Unfollow Tools or Browser Extensions? No. Use any HTTP client or the cURL, Node.js, Python, and Go examples. A third-party service still needs an approved connected account. Some HTTP clients offer free plans, but Xquik credits still apply. ### How Do I Manage Several Connected Accounts? Repeat approval for each acting account. Never reuse one action ID across accounts. ### How Do I Get an API Key? Create an Xquik API key in the dashboard. You can also send an OAuth 2.1 bearer token. ## Headers Send your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters The X user ID of the account to unfollow. ## Body X username or account ID of your connected account to act as. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Remove Twitter Likes API: Unlike a Tweet by ID Source: https://docs.xquik.com/api-reference/x-write/unlike DELETE /x/tweets/{id}/like Use the Remove Twitter Likes API to unlike one tweet by ID. Store the acting account, durable action, terminal result, billed credits, and retry decision. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "unlike", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "unlike", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "x_rate_limited", "message": "Rate limited by X. Wait before retrying." } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Use this Remove Twitter Likes API to unlike 1 tweet by ID. This Twitter unlike API changes 1 connected account's like state. Use it to delete Twitter likes 1 approved Tweet ID at a time. Use this route to delete likes on Twitter without deleting tweets. Clean up your Twitter likes without deleting tweets, replies, or reposts. It never deletes the tweet or another account's likes. Learn how to remove a like on Twitter through REST below. Compare X's separate [Unlike Post endpoint](https://docs.x.com/x-api/users/unlike-post). ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/x/tweets/1895432178065391234/like \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: unlike-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{"account":"elonmusk"}' | jq ``` ## Remove One Twitter Like by ID Put the exact Tweet ID in the path. Put its connected X account in the body. This endpoint accepts 1 Tweet ID per request. Store Tweet IDs as strings to preserve large identifiers. ## Authenticate the Twitter Account Send an Xquik API key or OAuth 2.1 bearer token. The request never accepts an X password or session cookie. Keep credentials in server-side code. Reconnect the account after `account_needs_reauth`. ## Approve the Exact Like Removal Show the tweet, author, Tweet ID, and acting account. Store the approver, approval time, and removal reason. Handle missing, private, or deleted tweets separately. Never replace an approved ID with another tweet. ## Save a Durable Unlike Receipt Save the action before changing your local like record. | Unlike action column | Source | Removal rule | | -------------------- | ------------------- | ---------------------------------------------- | | `acting_account` | Request `account` | Match the account that owns the original like. | | Tweet ID | Path `id` | Preserve the exact liked Tweet ID. | | `removal_reason` | Workflow value | Record why the user removed the like. | | `idempotency_key` | Request header | Create a fresh removal key. | | `write_action_id` | Response `id` | Store the durable removal action. | | `terminal` | Response `terminal` | Change the local state only when `true`. | ## Confirm Like Removal A `200` response is terminal. A `202` response still needs polling through `statusUrl`. Never resend an active action. After success, match the returned and requested Tweet IDs. If X reports no existing like, mark the account as unliked. Keep the prior state while `terminal` is `false`. Store the settled `billing` object after completion. Close already-unliked actions without another request. ## Remove Twitter Likes in a Controlled Queue This route has no mass-delete request. Process approved Tweet IDs separately. Give each account and Tweet ID a unique key. Use [User Likes](/api-reference/x/user-likes) to collect candidate IDs. Follow every cursor before approving the list. After `429`, wait for `Retry-After`. Never rotate accounts to bypass limits. Review X's separate [Unlike rate limits](https://docs.x.com/x-api/fundamentals/rate-limits). ## Prevent Duplicate Unlike Requests Create one idempotency key per account and Tweet ID. Reuse it only after an interrupted response. A `409` means that key protects another payload. Create a new key only for a new removal intent. ## Handle Remove Twitter Likes Errors Never infer failure from a client timeout. Poll the returned action before another request. * `400`: Correct the Tweet ID or account. * `401`: Replace invalid Xquik authentication. * `402`: Add credits before another unlike. * `403`: Reconnect the selected X account. * `409`: Keep the original action and payload. * `422`: Review X's rejection. * `429`: Honor `Retry-After` before retrying. * `500` or `503`: Follow `safeToRetry` and `terminal`. ## Choose Unlike, Delete, or Unretweet Use this route only to remove one account's like. Use [Like Tweet](/api-reference/x-write/like) to restore a like. Use [Delete Tweet](/api-reference/x-write/delete-tweet) for an owned tweet. Use [Unretweet](/api-reference/x-write/unretweet) for a repost. Unlike never removes replies, follows, bookmarks, or accounts. ## Remove Twitter Likes Questions ### How to remove a like on Twitter with an API? Send the Tweet ID and connected account. Poll the durable action until it becomes terminal. ### Can I remove all Twitter likes at once? No. Process each approved Tweet ID separately. Store one key and action per removal. ### Can I remove likes for multiple Twitter accounts? Yes. Name one connected account per request. Keep its approval, key, and receipt together. ### What are the risks of third-party like removal tools? Never share your X password or session cookie. Review every account and Tweet ID. ### Can I schedule Twitter like removal? Use your scheduler for approved Tweet IDs. This endpoint stores no future schedule. ### Does unliking delete the original tweet? No. It changes only the connected account's like state. ### Can I restore a removed like? Yes. Call [Like Tweet](/api-reference/x-write/like) after new approval. Use a new key. ### Does removing a like erase every historical record? No. Earlier logs, exports, screenshots, or audits may remain. Do not treat a Twitter archive as proof of the current like state. ## Headers Send your Xquik API key in this header. Alternatively, send an OAuth 2.1 bearer token. Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters The ID of the tweet to unlike. ## Body X username or account ID for the connected account. Xquik removes an included `@` prefix. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # How to Undo a Retweet with the Twitter API Source: https://docs.xquik.com/api-reference/x-write/unretweet DELETE /x/tweets/{id}/retweet Learn how to undo a retweet on Twitter by Tweet ID. Remove one repost, poll the write action, verify completion, and handle Twitter API undo retweet errors. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "unretweet", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "unretweet", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "x_rate_limited", "message": "Rate limited by X. Wait before retrying." } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Use this Twitter API undo retweet workflow for 1 repost by Tweet ID. Name the connected X account that owns the repost. Then poll `statusUrl` until `terminal` becomes `true`. The route preserves the source tweet and its original author. It does not remove likes, follows, replies, bookmarks, or original tweets. Compare X's separate [Unrepost Post endpoint](https://docs.x.com/x-api/users/unrepost-post). X also publishes a [Retweet management guide](https://docs.x.com/x-api/posts/retweets/quickstart/manage-retweets). ```bash cURL theme={null} curl -X DELETE https://xquik.com/api/v1/x/tweets/1895432178065391234/retweet \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: unretweet-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "elonmusk" }' | jq ``` ```javascript Node.js theme={null} const tweetId = "1895432178065391234"; const response = await fetch(`https://xquik.com/api/v1/x/tweets/${tweetId}/retweet`, { method: "DELETE", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "unretweet-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "elonmusk", }), }); const unretweetReceipt = await response.json(); if (!response.ok) { throw new Error(JSON.stringify(unretweetReceipt)); } ``` ```python Python theme={null} import requests tweet_id = "1895432178065391234" response = requests.delete( f"https://xquik.com/api/v1/x/tweets/{tweet_id}/retweet", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "unretweet-1895432178065391234", }, json={ "account": "elonmusk", }, ) unretweet_receipt = response.json() response.raise_for_status() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { tweetID := "1895432178065391234" body, _ := json.Marshal(map[string]interface{}{ "account": "elonmusk", }) req, err := http.NewRequest("DELETE", "https://xquik.com/api/v1/x/tweets/"+tweetID+"/retweet", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "unretweet-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var unretweetReceipt map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&unretweetReceipt); err != nil { panic(err) } fmt.Println(unretweetReceipt) } ``` ## Authenticate and Approve the Reposting Account Send an Xquik API key or OAuth 2.1 bearer token. Name 1 connected X account in the request body. Never send an X password or session cookie. Show the specific tweet and acting account before approval. Include the author, text, media, Tweet ID, and removal reason. ## Remove One Repost by Tweet ID Resolve the source Tweet ID before approval. Do not use a timeline position or search-result index. Send the ID as a string to preserve all digits. Generate a new idempotency key for the removal. Do not reuse the key from the original repost. Reuse the key when a response gets interrupted. Store the action ID before starting another removal. Poll `statusUrl` while `terminal` is `false`. ## Choose Unretweet, Delete Tweet, or Unlike Use Unretweet for an unchanged repost of someone else's tweet. It removes only the acting account's repost relationship. Use [Delete Tweet](/api-reference/x-write/delete-tweet) for original or quote tweets. Use [Unlike Tweet](/api-reference/x-write/unlike) for likes. X Premium's Undo Post feature is different. It delays publication during a short preview window. Unretweet removes a repost that already exists. ## Verify Repost Removal and Timeline State Store `id` as the durable write action ID. Poll the same `statusUrl` until `terminal` becomes `true`. Store the account, source Tweet ID, request hash, billing, and both action IDs. Close already-removed actions without another request. Keep the prior repost state after a final failure. A cached timeline can lag behind the write result. X documents that delay in its [repost help](https://help.x.com/en/using-x/how-to-repost). ## Schedule Repost Cleanup Without Bulk Unretweet Use your scheduler when a repost should end later. The endpoint accepts 1 Tweet ID per request. It has no bulk unretweet body. To delete retweets in a queue, approve each Tweet ID separately. ## Handle Twitter Undo Retweet Errors Fix the ID or account after `400`. Replace Xquik authentication after `401`; reconnect the account after `403`. Add credits after `402`; review rejected input after `422`. Honor `Retry-After` after `429`. After `500` or `503`, check `safeToRetry` before retrying. Use `nextAction` to choose the next step. ## Twitter Undo Retweet Questions ### How Do I Undo a Retweet on Twitter with an API? Approve the account and source Tweet ID. Send this DELETE request; poll until terminal. ### Can I Undo Recent, Old, or Several Retweets? Submit recent or old removals individually. The X Premium Undo Post window does not limit this endpoint. ### Why Can't I Undo a Retweet on Twitter? Check authentication, credits, account connection, and the source tweet. ### What Happens to the Tweet, Engagement, and Notifications? Unretweet keeps the source tweet and author intact. It does not change likes, replies, bookmarks, or source content. The action does not track notification delivery. Cached timelines can lag behind the terminal result. ### Do I Use a Twitter Archive, Retweet Icon, or Delete Button? A Twitter archive can help identify an old source Tweet ID. In X, use the green retweet icon for reposts. Use a delete button to delete tweets you published. Never delete the original source tweet during an unretweet. ## Headers Send your Xquik API key. Generate one from the [dashboard](https://xquik.com/dashboard). Send `Bearer ` instead of `x-api-key` when using OAuth 2.1. Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Path parameters Source Tweet ID to remove from the selected account's reposts. ## Body X username or account ID that owns the repost. The `@` prefix is optional. ## Response ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Profile Picture API: Update Avatar Images Source: https://docs.xquik.com/api-reference/x-write/update-avatar PATCH /x/profile/avatar Use the Twitter profile picture API to update an X avatar. Upload a JPEG or PNG file, or provide an HTTPS image URL, with authentication and polling examples. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "update_avatar", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "update_avatar", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
Use this Twitter profile picture API to update one connected account's avatar. Choose this Twitter API profile image route for automated avatar changes. A Twitter API update profile image request accepts a JPEG or PNG file. It may also use a fetchable HTTPS image URL. The maximum image size is 700 KB. X recommends a 400 × 400 pixel profile picture. ## Update a Twitter Profile Picture Through the API Call `PATCH /x/profile/avatar` to replace the square profile image. Use [Update Banner](/api-reference/x-write/update-banner) for the header image. Use [Update Profile](/api-reference/x-write/update-profile) for names and other public text fields. This route never retrieves another user's avatar. **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl -X PATCH https://xquik.com/api/v1/x/profile/avatar \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: avatar-update-1895432178065391234" \ -F "account=myxaccount" \ -F "file=@avatar.png" | jq ``` ```javascript Node.js theme={null} const fs = require("fs"); const FormData = require("form-data"); const form = new FormData(); form.append("account", "myxaccount"); form.append("file", fs.createReadStream("avatar.png")); const response = await fetch("https://xquik.com/api/v1/x/profile/avatar", { method: "PATCH", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "avatar-update-1895432178065391234", }, body: form, }); const data = await response.json(); ``` ```python Python theme={null} import requests with open("avatar.png", "rb") as f: response = requests.patch( "https://xquik.com/api/v1/x/profile/avatar", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "avatar-update-1895432178065391234", }, files={"file": ("avatar.png", f, "image/png")}, data={"account": "myxaccount"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { var buf bytes.Buffer writer := multipart.NewWriter(&buf) writer.WriteField("account", "myxaccount") file, err := os.Open("avatar.png") if err != nil { panic(err) } defer file.Close() part, err := writer.CreateFormFile("file", "avatar.png") if err != nil { panic(err) } io.Copy(part, file) writer.Close() req, err := http.NewRequest("PATCH", "https://xquik.com/api/v1/x/profile/avatar", &buf) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "avatar-update-1895432178065391234") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ```bash URL Upload theme={null} curl -X PATCH https://xquik.com/api/v1/x/profile/avatar \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: avatar-url-update-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxaccount", "url": "https://example.com/avatar.png" }' | jq ``` ## Prepare a Twitter Avatar Image Choose one direct file or one fetchable HTTPS URL. Never send both sources. Accept only JPEG or PNG images. Reject GIF, WebP, and files above 700 KB. Review the square crop before approval. Keep faces and important marks near the center. Use only images you own or may publish. Read X's [profile image guidance](https://help.x.com/articles/166743) before uploading. ## Automate a Safe Profile Picture Update Create one idempotency key for the selected account and image. Reuse it only after an exact network interruption. Generate a new key after changing the image or account. A 200 response is terminal. Poll `statusUrl` after 202 until `terminal` becomes true. Never start another avatar update before that result. ## Verify the Updated Twitter Profile Image After a terminal write, use [Twitter Profile Lookup](/api-reference/x/twitter-profile-lookup). Open its `profilePicture` URL. Compare the rendered avatar with the approved source. Review the public square crop. Keep the action ID and verification timestamp. X may return a profile image URL like `https://pbs.twimg.com/profile_images/example.jpg`. | Profile avatar update column | Request or response source | Review rule | | ---------------------------- | -------------------------- | ----------------------------------- | | Acting X account | Request `account` | Confirm the intended profile. | | Uploaded file | Request `file` | Accept JPEG or PNG up to 700 KB. | | Image URL | Request `url` | Use one fetchable HTTPS source. | | Source choice | `file` or `url` | Send exactly one image source. | | Idempotency key | Request header | Change it after editing the image. | | Write action ID | Response `id` | Poll the matching lifecycle record. | | Profile result | Later profile lookup | Confirm the public avatar URL. | ## Fix Twitter Profile Picture API Errors Fix invalid image fields after 400. Replace authentication after 401. Add credits after 402. Reconnect after 403. Connect a missing account after 404. Keep the original action after 409. Replace rejected media after 422. Honor `Retry-After` after 429. Check `safeToRetry` after 500 or 503. ## Twitter Profile Picture API Questions ### How do I authenticate an avatar update? Send an `x-api-key` header or OAuth bearer token. The `account` field selects the connected profile. Never place credentials inside an image URL. ### Can I update several Twitter profile pictures in one request? No. Each request updates one connected account. Give each request its own idempotency key. Wait for each account's terminal result. ### Can I use usernames or user IDs for avatar updates? Yes. Set `account` to a connected username or numeric user ID. The selected user's profile receives the new image. Verify that identity before uploading. ### Can I update a Twitter profile image with Python or an SDK? Yes. Call the REST API with `import requests`, as shown above. Generated SDKs can send the same multipart file, account, API key, and idempotency key. ### Does this route retrieve Twitter profile pictures? No. It changes one connected account's avatar. Use Twitter Profile Lookup to retrieve a public `profilePicture` URL. ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended write. Reuse it only for an exact network replay. Use `multipart/form-data` for file uploads or `application/json` for URL uploads. Most HTTP clients set the multipart boundary automatically. ## Body X username or account ID of your connected account to act as. Multipart upload file. Required unless `url` is provided. Accepted formats: JPEG, PNG. Maximum file size: 700 KB. HTTPS image URL. Required unless `file` is provided. The URL must use HTTPS and remain directly fetchable. ## Response Connect the requested account, then submit a newly approved write. ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Profile Banner API: Update Header Images Source: https://docs.xquik.com/api-reference/x-write/update-banner PATCH /x/profile/banner Use the Twitter profile banner API to update an X header image. Upload JPEG or PNG, verify 1500 × 500 pixel dimensions, and poll asynchronous write responses. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "update_banner", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "update_banner", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
Use this Twitter profile banner API to update one connected account's header image. Upload a JPEG or PNG file, or provide a fetchable HTTPS image URL. The maximum file size is 2 MB. X recommends 1500 × 500 pixels for profile banners. ## Update a Twitter Profile Banner Through the API Call `PATCH /x/profile/banner` to replace the wide header image. Use [Update Avatar](/api-reference/x-write/update-avatar) for the profile picture. Use [Update Profile](/api-reference/x-write/update-profile) for names and other public text fields. This route never retrieves another user's banner. **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl -X PATCH https://xquik.com/api/v1/x/profile/banner \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: banner-update-1895432178065391234" \ -F "account=myxaccount" \ -F "file=@banner.png" | jq ``` ```javascript Node.js theme={null} const fs = require("fs"); const FormData = require("form-data"); const form = new FormData(); form.append("account", "myxaccount"); form.append("file", fs.createReadStream("banner.png")); const response = await fetch("https://xquik.com/api/v1/x/profile/banner", { method: "PATCH", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "banner-update-1895432178065391234", }, body: form, }); const data = await response.json(); ``` ```python Python theme={null} import requests with open("banner.png", "rb") as f: response = requests.patch( "https://xquik.com/api/v1/x/profile/banner", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "banner-update-1895432178065391234", }, files={"file": ("banner.png", f, "image/png")}, data={"account": "myxaccount"}, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { var buf bytes.Buffer writer := multipart.NewWriter(&buf) writer.WriteField("account", "myxaccount") file, err := os.Open("banner.png") if err != nil { panic(err) } defer file.Close() part, err := writer.CreateFormFile("file", "banner.png") if err != nil { panic(err) } io.Copy(part, file) writer.Close() req, err := http.NewRequest("PATCH", "https://xquik.com/api/v1/x/profile/banner", &buf) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "banner-update-1895432178065391234") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ```bash URL Upload theme={null} curl -X PATCH https://xquik.com/api/v1/x/profile/banner \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: banner-url-update-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxaccount", "url": "https://example.com/banner.png" }' | jq ``` ## Prepare Twitter Profile Banner Dimensions Use a 1500 × 500 pixel canvas. This Twitter banner size has a 3:1 aspect ratio. These Twitter profile banner dimensions match X's recommended header dimensions. The Twitter profile banner size must remain at 2 MB or smaller. Preserve high resolution while meeting that file size. Accept only JPEG or PNG image formatting. This route does not support animated GIFs. Export JPEG or PNG with your design tool or Twitter header template. Review every template export before uploading. Read X's [profile banner guidance](https://help.x.com/articles/166743) for current layout recommendations. Preview the banner on desktop and a mobile device. The profile picture can cover the bottom left corner. Keep faces, logos, and text outside that area. Different banner sizes or crops can hide important artwork. Validate the Twitter header size before queueing the write. A saved 1500 x 500 pixels template can save time on recurring updates. Keep one perfectly sized source for each approved campaign. Export banner images from that source instead of resizing previous uploads. Record the header dimensions and final file size beside the source. A social media platform preview cannot replace the desktop and mobile checks above. Keep the account's online presence consistent with approved logos and colors. Check the image size before upload. Do not assume a banner will boost engagement. Measure profile visits and follows separately. ## Automate a Safe Twitter Banner Update Create one idempotency key for the selected account and banner. Reuse it only after an exact network interruption. Generate a new key after changing the image or account. A 200 response is terminal. Poll `statusUrl` after 202 until `terminal` becomes true. ## Verify the Updated Twitter Banner After a terminal write, use [Twitter Profile Lookup](/api-reference/x/twitter-profile-lookup). Open its `profileBannerUrl` value. Compare the rendered header with the approved source. Check desktop and mobile crops. Store the action ID and verification timestamp. | Profile banner update column | Request or response source | Review rule | | ---------------------------- | -------------------------- | ----------------------------------- | | Acting X account | Request `account` | Confirm the intended profile. | | Uploaded file | Request `file` | Accept JPEG or PNG up to 2 MB. | | Image URL | Request `url` | Use one fetchable HTTPS source. | | Source choice | `file` or `url` | Send exactly one image source. | | Idempotency key | Request header | Change it after editing the image. | | Write action ID | Response `id` | Poll the matching lifecycle record. | | Profile result | Later profile lookup | Confirm the public banner URL. | ## Fix Twitter Profile Banner API Errors Fix invalid image fields after 400. Replace authentication after 401. Add credits after 402. Reconnect after 403. Connect a missing account after 404. Keep the original action after 409. Replace rejected media after 422. Honor `Retry-After` after 429. Check `safeToRetry` after 500 or 503. ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended write. Reuse it only for an exact network replay. Use `multipart/form-data` for file uploads or `application/json` for URL uploads. Most HTTP clients set the multipart boundary automatically. ## Body X username or account ID of your connected account to act as. Multipart upload file. Required unless `url` is provided. Accepted formats: JPEG, PNG. Maximum file size: 2 MB. HTTPS image URL. Required unless `file` is provided. The URL must use HTTPS and remain directly fetchable. ## Response Connect the requested account, then submit a newly approved write. ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Profile Update API: Name, Bio, Location, URL Source: https://docs.xquik.com/api-reference/x-write/update-profile PATCH /x/profile Update a Twitter profile bio, display name, location, or website safely through Xquik. Send exact fields, poll write actions, and handle every API response. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "update_profile", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "update_profile", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
Use this Twitter profile update API for public profile text. Change a display name, bio, location, or website in one request. Send only the fields that need new values. Omitted fields remain unchanged. The connected account receives every approved change. This route does not change a username, avatar, banner, or birth date. Use [Update Avatar](/api-reference/x-write/update-avatar) or [Update Banner](/api-reference/x-write/update-banner) for profile images. Read X's [profile customization guide](https://help.x.com/articles/166743) before updating profile text. ## Update Twitter Profile Text Call `PATCH /x/profile` to update Twitter profile fields programmatically. Provide the connected account plus at least one supported field. The `name` field changes the display name, not the `@username`. The `description` field changes the public bio. The `location` and `url` fields change their matching public labels. **10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl -X PATCH https://xquik.com/api/v1/x/profile \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: profile-update-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{ "account": "myxaccount", "name": "New Display Name", "description": "Building cool things", "location": "San Francisco", "url": "https://example.com" }' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/profile", { method: "PATCH", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "profile-update-1895432178065391234", "Content-Type": "application/json", }, body: JSON.stringify({ account: "myxaccount", name: "New Display Name", description: "Building cool things", location: "San Francisco", url: "https://example.com", }), }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.patch( "https://xquik.com/api/v1/x/profile", headers={ "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "profile-update-1895432178065391234", }, json={ "account": "myxaccount", "name": "New Display Name", "description": "Building cool things", "location": "San Francisco", "url": "https://example.com", }, ) data = response.json() ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "account": "myxaccount", "name": "New Display Name", "description": "Building cool things", "location": "San Francisco", "url": "https://example.com", }) req, err := http.NewRequest("PATCH", "https://xquik.com/api/v1/x/profile", bytes.NewReader(body)) if err != nil { panic(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Idempotency-Key", "profile-update-1895432178065391234") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) } fmt.Println(data) } ``` ## Review Every Profile Field Review the exact display name, bio, location, and website before approval. Names allow 1 to 50 characters. Bios allow 160 characters. Locations allow 30 characters. Website values must be valid URLs. Empty `description` or `location` values clear those fields. An empty `name` or invalid URL returns 400\. Keep API keys and private notes outside public profiles. | X profile text update column | Request or response source | Review rule | | ---------------------------- | -------------------------- | -------------------------------------- | | Acting X account | Request `account` | Confirm the intended profile. | | Display name | Request `name` | Keep 1 to 50 characters. | | Profile bio | Request `description` | Keep 160 characters or fewer. | | Location | Request `location` | Keep 30 characters or fewer. | | Website | Request `url` | Verify the public destination. | | Changed fields | Exact request keys | Leave unrelated profile fields absent. | | Idempotency key | Request header | Changed field values use another key. | | Write action ID | Response `id` | Poll the matching lifecycle record. | ## Automate a Safe Profile Update Create one idempotency key for each account and field set. Reuse that key after a network interruption. Workers handling the same update must share it. Change the key after editing any value. A 200 response is terminal. Poll `statusUrl` after 202 until `terminal` becomes true. ## Coordinate Updates Across Connected Accounts Send one approved request for each connected X account. Give every request a key for its exact account and field set. Wait for an account's terminal result before starting its next profile change. Other accounts may update in parallel with different keys. Store the account, requested fields, request hash, write action ID, and status. Workers use this record to avoid duplicate profile updates. It also shows which display name, bio, location, or website each action changed. Give different teams their own API keys. Never share credentials between unrelated profile workflows. ## Verify the Saved Twitter Profile After a terminal write, use [Twitter Profile Lookup](/api-reference/x/twitter-profile-lookup). Compare its name, description, location, and website with the approved request. This check works across user profiles, including user-owned brand accounts. Profile updates never change posts, tweets, followers, replies, likes, or lists. For a campaign, save earlier values and restore them with a new key. Store the earlier and replacement values with the action ID. Apply the replacement at launch, then verify the public profile. Restore earlier fields with a new idempotency key after the campaign. This history makes each temporary Twitter profile update reversible. Treat the write action as lifecycle evidence. Treat the later profile lookup as public proof. Save both timestamps when profile changes need an audit trail. Never store authentication tokens with those public profile values. ## Fix Twitter Profile Update Failures Fix rejected fields after 400. Replace authentication after 401. Add credits after 402. Reconnect after 403. Connect a missing account after 404. Keep the original action after 409. Honor `Retry-After` after 429. Check `safeToRetry` after 500 or 503. Delete fake verification marks or unsafe profile links, then retry. X documents more causes in its [profile save troubleshooting guide](https://help.x.com/en/managing-your-account/cant-save-changes-to-my-account). Do not create a new action after an uncertain network response. Check the saved `statusUrl` before creating another action. The same request may reuse its original idempotency key. Change the key after correcting any field. Retry a server error only when `safeToRetry` allows it. ## Twitter API Update Profile Questions ### What endpoint changes a Twitter display name or bio? Send `name` or `description` to `PATCH /x/profile`. The `name` field changes the display name, not the `@username`. An empty `description` clears the bio. ### Can I automate recurring Twitter bio changes? Yes. Approve every replacement value. Use a new key for each scheduled change. Check the action and public profile. Keep the earlier bio for rollback. ### Can this Twitter API change an account username? No. The `name` field changes only the public display name. It never changes the `@username` or numeric account ID. Keep username changes outside this workflow. ### Does a profile update change tweets or follower counts? No. Existing tweets, replies, followers, following, likes, lists, and communities stay unchanged. A later profile lookup may return those counts, but this route cannot edit them. ### Can this endpoint update a profile picture or banner? No. Use the dedicated avatar or banner route. Those endpoints validate image type and size separately. ### Which authentication works with this Twitter API route? Use an `x-api-key` header or an OAuth bearer token. The chosen connected X account supplies the profile identity. Generated SDKs can send the same fields and headers. ### How should I schedule temporary profile changes? Check both values before adding them to the schedule. Use one key for the launch action and a different key for the rollback. Poll each action until terminal, then verify the public profile before continuing. ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended write. Reuse it only for an exact network replay. Must be `application/json`. ## Body X username or account ID of your connected account to act as. Display name. Maximum 50 characters. Profile bio. Maximum 160 characters. Profile location. Maximum 30 characters. Website URL. Must be a valid URL. ## Response Connect the requested account, then submit a newly approved write. ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Media Upload API for Tweets, Replies & DMs Source: https://docs.xquik.com/api-reference/x-write/upload-media POST /x/media Use the Twitter media upload API for images, GIFs, and MP4 videos. Return mediaUrl for tweets and replies or mediaId for one direct message attachment. ```json theme={null} { "object": "x_write_action", "id": "12345", "writeActionId": "12345", "action": "upload_media", "status": "success", "terminal": true, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12345", "pollAfterMs": null } ``` ```json theme={null} { "object": "x_write_action", "id": "12346", "writeActionId": "12346", "action": "upload_media", "status": "dispatching", "terminal": false, "retryable": false, "safeToRetry": false, "statusUrl": "/api/v1/x/write-actions/12346", "pollAfterMs": 2000 } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check request fields.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "account_needs_reauth", "message": "X account needs re-authentication. Re-add the account." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "idempotency_conflict", "message": "Idempotency-Key was already used with a different request.", "charged": false, "chargedCredits": "0", "retryable": false, "safeToRetry": true } ``` ```json theme={null} { "error": "x_rejected", "message": "X rejected this request. Wait a few minutes and try again." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_write_failed", "message": "Write action failed unexpectedly. Contact support if this persists." } ``` ```json theme={null} { "error": "write_tracking_unavailable", "message": "Write tracking unavailable. Try again.", "charged": false, "chargedCredits": "0", "retryable": true, "safeToRetry": true } ```
For the complete documentation index, see llms.txt.
**10 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Call `POST /x/media`. This Twitter API media upload route accepts one local file or hosted HTTPS media URL. A Twitter API upload media request uses one connected X account. A completed upload returns a media ID and reusable `mediaUrl`. Skip this endpoint when a tweet already has public HTTPS media URLs. Call [Create Tweet](/api-reference/x-write/create-tweet) directly with those URLs. ## Use the Twitter API Upload Media Workflow Send `multipart/form-data` for a file. Send `application/json` for a hosted URL. Authenticate with an API key or OAuth bearer token. Add one `Idempotency-Key` per intended upload. Any HTTP client works. ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/media \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: media-upload-1895432178065391234" \ -F "account=myxhandle" \ -F "file=@/path/to/image.png" | jq ``` ```javascript Node.js theme={null} const form = new FormData(); form.append("account", "myxhandle"); form.append("file", new Blob([fileBuffer]), "image.png"); const response = await fetch("https://xquik.com/api/v1/x/media", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Idempotency-Key": "media-upload-1895432178065391234", }, body: form, }); if (!response.ok) { throw new Error(await response.text()); } const { mediaId, mediaUrl } = await response.json(); ``` ### Upload a Hosted Media URL ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/x/media \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Idempotency-Key: media-url-1895432178065391234" \ -H "Content-Type: application/json" \ -d '{"account": "myxhandle", "url": "https://example.com/image.png"}' | jq ``` Both inputs return the same lifecycle record. Poll `statusUrl` after 202. Store `mediaId`, `mediaUrl`, the account, and the idempotency key. ## Handle Media Types, Large Videos & Multiple Images Supported media files include AVIF, GIF, JPEG, PNG, WebP, and MP4. Every media type requires validation before upload. Match the content type to the file. Use a public URL. Private and reserved addresses are rejected. The download timeout is 30 seconds. The file limit is 15,728,640 bytes. Set `is_long_video` to `true` for MP4 files longer than 140 seconds. Xquik handles the chunked upload and media category internally. Send one request, then poll its lifecycle. For multiple images, call once per file. Collect up to 4 `mediaUrl` values. Send them together in Create Tweet. DMs accept exactly one media ID. Schedulers store `mediaUrl` until send time. This endpoint does not schedule tweets. ## Store the Twitter API Media Upload Receipt | Output | Next request | Rule | | ---------- | ---------------------- | -------------------------------------------------- | | `mediaUrl` | Tweet or reply `media` | Send up to 4 image URLs or 1 MP4 URL up to 100 MB. | | `mediaId` | DM `media_ids` | Send exactly 1 media ID. | Store the account, success, source reference, and idempotency key with each receipt. For replies, add `reply_to_tweet_id` in Create Tweet. Never send `media_ids` to Create Tweet. Use public `mediaUrl` values. Tweet and reply writes cost 30 credits, plus media surcharges. DM writes cost 10 credits. ## Fix Twitter Media Upload API Errors Fix invalid fields or content types after 400. Replace credentials after 401. Add credits after 402. Reconnect the X account after 403. Connect a missing account after 404. Keep the original action after 409. Replace unreachable or private URLs after `422 media_download_failed`. Replace rejected media after other 422 errors. Honor `Retry-After` after 429. Check `safeToRetry` after 500 or 503. ## Headers Your API key. OAuth bearer authentication is also supported. Generate a key from the [dashboard](https://xquik.com/dashboard). Unique key for this intended write. Reuse it only for an exact network replay. Use `multipart/form-data` when uploading a file. Use `application/json` when providing a URL. ## Body A connected X username or account ID. The account performs the upload. Required without `url`. Accepts AVIF, GIF, JPEG, PNG, WebP, or MP4. Required without `file`. Provide a public HTTPS URL. AI agents and MCP clients can send URLs instead of binary uploads. Multipart MP4 uploads only. Set `true` when the video exceeds 140 seconds. Defaults to `false`. ## Response Connect the requested account, then submit a newly approved write. ## Durable Write Recovery Send one unique `Idempotency-Key` per intended write. Reuse it only for the same account, target, payload, and media. 1. Store `id`, the nested `hash` in `request`, `billing`, and `statusUrl`. 2. Poll after `Retry-After` or `pollAfterMs` when `terminal` is `false`. 3. Trust `safeToRetry` and `nextAction` before any new write. ### 200 Terminal or 202 Active Store terminal results. Poll active actions without creating another write. * After HTTP `200`, store the result and settled billing. * After HTTP `202`, poll the same action. Never submit another write. * After HTTP `400`, fix the named field. Use a new idempotency key. * After HTTP `401`, fix authentication. Do not retry unchanged. * After HTTP `402`, fund the account before another write. * After HTTP `403`, reconnect the account. * After HTTP `409`, keep the original action. Use a new key for new input. * After HTTP `422`, fix the rejected request before retrying. * After HTTP `429`, wait for `Retry-After`. Preserve the same key. * After HTTP `500`, retry only when `safeToRetry` is `true`. * After HTTP `503`, poll while `terminal` is `false`. See [Get Write Action Status](/api-reference/x-write/get-write-action-status) for every lifecycle field, terminal state, billing field, and retry rule. # Twitter Batch Tweet Lookup API & Post Details Source: https://docs.xquik.com/api-reference/x/batch-tweets GET /x/tweets Retrieve up to 100 tweets by ID in one request with full text, authors, media, reply and quote context, engagement metrics, and URLs. See request fields. ```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": "" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
## Choose Exact Batch Lookup Use this route for exact lookups of up to 100 tweet IDs. It preserves ID-based batching and charges only returned tweets. Use list-tweets when reading a list feed instead. 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`. **1 credit per tweet returned** · Accepts account credits and guest `paid_reads` ```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] ``` The Node.js and Python snippets shape durable tweet rows instead of printing full response pages. Persist `tweetRows` or `tweet_rows` and `missingIds` or `missing_ids` with the original ID list so retries only request missing tweets. ## Direct batch tweet handoff Use `GET /x/tweets` when a CRM, warehouse, newsroom, moderation queue, or agent workflow already has tweet IDs and needs tweet text, authors, metrics, media URLs, and missing-ID handling in one response. Use [Get Tweet](/api-reference/x/get-tweet) when you need one tweet by ID, or [Search Tweets](/api-reference/x/search-tweets) when you need 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` instead of relying 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 | Preserve all requested IDs, including missing ones. | | `tweet_id` | `tweets[].id` | Join a returned tweet to its request row. | | `text` | `tweets[].text` | Preserve 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` | Preserve the tweet publication time. | | `conversation_id` | `tweets[].conversationId` | Join returned tweets to conversation workflows. | | `media_urls` | `tweets[].media[].mediaUrl` | Preserve 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 | De-duplicate before sending; preserve 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 reconcile missing IDs explicitly. | | Zero affordable results | Stop on `402 insufficient_credits` and fund the account before retrying. | ## Query parameters Comma-separated tweet IDs. Maximum 100 per request. ## Headers Full account API key. Session cookie and OAuth authentication are also supported. Send `Bearer xq_your_guest_key_here` for an active `paid_reads` guest key. ## Response ### 200 OK Array of tweets matching the requested IDs. **Tweet object fields:** Tweet ID. Tweet text. Tweet type. Omitted if unavailable. ISO 8601 creation timestamp. Whether this is a Note Tweet. Omitted if unavailable. Like count. Omitted if unavailable. Retweet count. Omitted if unavailable. Reply count. Omitted if unavailable. Quote tweet count. Omitted if unavailable. View count. Omitted if unavailable. Bookmark count. Omitted if unavailable. Permalink URL on X. Omitted if unavailable. Tweet language code. Omitted if unavailable. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. User ID being replied to. Omitted if unavailable. Username being replied to. Omitted if unavailable. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author handle without `@`. Author display name. Omitted if unavailable. Follower count. Omitted if unavailable. Whether the author is verified. Omitted if unavailable. Author profile image URL. Omitted if unavailable. Media attachments. Omitted when the tweet has no media. **Media object fields:** Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Always `false` for batch requests. Always empty for batch requests. ```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" } ``` ### 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Batch Users](/api-reference/x/batch-users) · [Get Tweet](/api-reference/x/get-tweet) # Twitter Batch User Lookup API & Profile Details Source: https://docs.xquik.com/api-reference/x/batch-users GET /x/users/batch Retrieve up to 100 X user profiles by ID in one request, including usernames, bios, verification state, follower counts, and profile media. See costs. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": false, "next_cursor": "", "requested_count": 2, "processed_count": 2, "returned_count": 1, "unavailable_ids": [ "1234567890" ], "unprocessed_ids": [] } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
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`. **1 credit per user returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl "https://xquik.com/api/v1/x/users/batch?ids=44196397,987654321" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const ids = ["44196397", "987654321"]; const response = await fetch( `https://xquik.com/api/v1/x/users/batch?ids=${ids.join(",")}`, { headers: { "x-api-key": "xq_your_api_key_here" }, } ); const data = await response.json(); const profilesById = new Map(data.users.map((user) => [user.id, user])); const profileRows = data.users.map((user) => ({ user_id: user.id, username: user.username, display_name: user.name, bio: user.description ?? null, follower_count: user.followers ?? null, following_count: user.following ?? null, verified: user.verified ?? false, profile_image_url: user.profilePicture ?? null, })); const missingIds = ids.filter((id) => !profilesById.has(id)); ``` ```python Python theme={null} import requests ids = ["44196397", "987654321"] response = requests.get( "https://xquik.com/api/v1/x/users/batch", params={"ids": ",".join(ids)}, headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() profiles_by_id = {user["id"]: user for user in data["users"]} profile_rows = [ { "user_id": user["id"], "username": user["username"], "display_name": user["name"], "bio": user.get("description"), "follower_count": user.get("followers"), "following_count": user.get("following"), "verified": user.get("verified", False), "profile_image_url": user.get("profilePicture"), } for user in data["users"] ] missing_ids = [user_id for user_id in ids if user_id not in profiles_by_id] ``` The Node.js and Python snippets shape durable profile rows instead of printing full profile objects. Persist `profileRows` or `profile_rows` and `missingIds` or `missing_ids` with the original ID list so retries only request missing profiles. ## Direct batch user handoff Use `GET /x/users/batch` when a CRM, warehouse, enrichment, lead scoring, or agent workflow already has numeric X user IDs and needs profile details in one JSON response. Use [Get User](/api-reference/x/twitter-profile-lookup) when you have one ID or username to resolve. Store `requested_ids`, `user_id`, `username`, `display_name`, profile metrics, verification state, `profile_image_url`, `has_next_page`, and `next_cursor`. Join returned users by `user_id` instead of relying 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`. Send comma-separated X user IDs in `ids`. Keep the original list as `requested_ids` for retry and audit rows. Store each returned `id` as `user_id` with `username`, `name`, `description`, `followers`, `following`, `verified`, and `verifiedType`. Compare returned `user_id` values with `requested_ids`. Retry only the missing IDs or route username-only inputs to Get User first. Use the single-page batch contract: `has_next_page: false` and `next_cursor: ""`. Do not treat it as a cursor workflow. ## Which lookup endpoint? Use [`GET /x/users/{id}`](/api-reference/x/twitter-profile-lookup) for one username or one user ID. Use `GET /x/users/batch` for up to 100 comma-separated user IDs in one request. Use [`GET /x/users/search`](/api-reference/x/search-users) before batch lookup when the workflow starts from a name, brand, or handle fragment. Use [`GET /x/tweets/batch`](/api-reference/x/batch-tweets) when the input list contains tweet IDs instead of user IDs. Use [`GET /x/users/{id}/followers`](/api-reference/x/followers) or [`GET /x/users/{id}/following`](/api-reference/x/following) when the job starts from an account audience. Use [`Create extraction`](/api-reference/extractions/create) when the source is a follower, following, timeline, media, or search job instead of an existing ID list. ## Enrich a known set of user IDs Use batch lookup after another workflow already identified exact profiles. Examples include follower exports, tweet authors, community members, or CRM records. Prepare one deduplicated ID list. Keep the original source beside each ID. After lookup, map every returned profile back to its source row. Useful enrichment fields include: * Current username and profile name. * Biography, location, and verification state. * Follower and following counts. * Profile image and account creation time. Treat missing IDs explicitly. Do not shift remaining profiles into another row’s position. Join responses by numeric user ID. Split oversized workloads into supported batches. Save each completed batch before starting the next. This makes a failed enrichment job safe to resume. ## Query parameters Comma-separated user IDs. Maximum 100 per request. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of user profiles matching the requested IDs. **User object fields:** X user ID. X username. Display name. Profile bio. Follower count. Following count. Verified status. Profile image URL. Profile location. Account creation date (ISO 8601). Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines. Omitted if unavailable. Whether the user is an X translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. IDs of pinned tweets. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable. Omitted if available. Reason the account is unavailable. Omitted if available. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. Role within the requested community context. Omitted outside community results. Always `false` for batch requests. Always empty for batch requests. ```json theme={null} { "users": [ { "id": "44196397", "username": "elonmusk", "name": "Elon Musk", "followers": 200000000, "verified": true } ], "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" } ``` ### 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 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Batch Tweets](/api-reference/x/batch-tweets) · [Get User](/api-reference/x/twitter-profile-lookup) # Twitter Bookmark Folders API & Saved Tweet Export Source: https://docs.xquik.com/api-reference/x/bookmark-folders GET /x/bookmarks/folders List Twitter bookmark folders by ID and name. Export each folder's saved tweets with authors, replies, likes, reposts, views, media & cursor checkpoints. ```json theme={null} { "folders": [ { "id": "1234567890", "name": "Read Later" } ], "has_next_page": false, "next_cursor": "" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* 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)
List Twitter bookmark folders for one connected account. Each row contains a folder ID and name for a saved tweet export. [X documents bookmark folders as private, authenticated-user content.](https://docs.x.com/x-api/posts/bookmarks/introduction) **1 credit per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Requires a connected X account. Use folder IDs from this response with `GET /x/bookmarks?folderId=...` to read tweets saved inside a folder. ```bash cURL theme={null} curl https://xquik.com/api/v1/x/bookmarks/folders \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/x/bookmarks/folders", { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const data = await response.json(); const folderRows = data.folders.map((folder) => ({ folder_id: folder.id, folder_name: folder.name, bookmarks_endpoint: `/x/bookmarks?folderId=${encodeURIComponent(folder.id)}`, has_more_folders: data.has_next_page, })); for (const row of folderRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } ``` ```python Python theme={null} import json import requests response = requests.get( "https://xquik.com/api/v1/x/bookmarks/folders", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() folder_rows = [ { "folder_id": folder["id"], "folder_name": folder["name"], "bookmarks_endpoint": f"/x/bookmarks?folderId={folder['id']}", "has_more_folders": data["has_next_page"], } for folder in data["folders"] ] for row in folder_rows: print(json.dumps(row)) ``` The Node.js and Python snippets shape durable bookmark folder rows instead of printing the full response. Persist `folderRows` or `folder_rows`. Then pass `folder_id` into `GET /x/bookmarks?folderId=...`. ## Direct bookmark folder handoff Use `GET /x/bookmarks/folders` for an authenticated X account. It serves a saved-tweet workflow, CRM enrichment job, research queue, or agent. Store `folder_id` and `folder_name` with the connected account ID. The route returns one folder page. `has_next_page` stays `false`, and `next_cursor` stays empty. Use each folder ID with [Bookmarks](/api-reference/x/bookmarks) to fetch its saved tweets. ## Twitter Bookmark Folder Workflow ### 1. Discover Existing Folders Call the route with the connected account's Xquik API key. It lists that account's private folders. It cannot create, rename, move, or delete folders. An empty `folders` array means the account returned no folders. ### 2. Build a Folder Index Use `folder_id` as the stable join key. Folder names can change or repeat. Protect folder names like saved tweets. Exclude them from public logs, URLs, and shared filenames. ### 3. Export Saved Tweets by Folder Pass each folder ID to `GET /x/bookmarks?folderId=...`. Saved tweet rows can include text, authors, likes, replies, reposts, views, media, and cursors. Write the folder ID beside every row to preserve folder membership. ### 4. Resume and Audit the Export The folder list has no next page. The bookmarked-tweet route paginates per folder. Store its `next_cursor`, collection time, and connected account ID. ## Plan a Reliable Twitter Bookmark Folder Export Treat every folder list as an account-specific snapshot. Compare folder IDs with the preceding snapshot. Match renamed folders by ID. A missing ID only means the current snapshot omitted it. Keep prior exports until verification. ### Store One Checkpoint per Folder Store the folder ID, bookmark cursor, completion state, and collection time. Retry only the incomplete folder after `424`, `429`, or `502` responses. Upsert rows by account, folder, and tweet IDs. Save every row before advancing the cursor. ### Define Folder Export Records Store `account_id`, `folder_id`, and `folder_name` beside each saved tweet. Preserve tweet, author, engagement, media, and timestamp fields. Store `source_cursor` beside each page. Validate IDs before exporting to a spreadsheet, CRM, research queue, or storage system. ## Twitter Bookmark Folder Questions ### Do Twitter Bookmarks Have Folders? Yes. This endpoint lists the named folders visible to the connected account. ### Can This API Create or Rename Bookmark Folders? No. This route only reads folder IDs and names. ### Can I Access Folders from Mobile and Desktop? Yes. The endpoint works independently of the device. It returns folders available to the connected account. ### How Do I Back Up Twitter Bookmark Folders? Save the folder index as JSON Lines or CSV. Export each folder's saved tweets. Join both files by `folder_id`. Include the account ID and collection time. ### What Errors Can Stop a Folder Export? A `401` response means the key cannot access the connected account. A `402` response requires more credits. Respect retry guidance in a `429` response. Retry temporary `424` or `502` read failures later. ## Headers Your API key. Session cookie authentication is also supported. ## Response ### 200 OK Array of bookmark folders. **Folder object fields:** This value contains the folder ID. This value contains the folder name. Always `false` for the current bookmark folder route. Always an empty string for the current bookmark folder route. **Related:** [Bookmarks](/api-reference/x/bookmarks) · [Timeline](/api-reference/x/timeline) # Export Twitter Bookmarks with Twitter Bookmarks API Source: https://docs.xquik.com/api-reference/x/bookmarks GET /x/bookmarks Export Twitter bookmarks with tweet text, authors, replies, likes, reposts, views, media, folders, CSV rows & cursor checkpoints from one connected account. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* 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)
Export Twitter bookmarks from one connected account. This Twitter bookmarks API returns saved tweets, authors, likes, replies, reposts, views, media, and cursor checkpoints. [The Twitter developer reference defines bookmarks as private, authenticated-user content.](https://docs.x.com/x-api/posts/bookmarks/introduction) 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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Requires a connected X account. Uses user-authenticated access. ```bash cURL theme={null} curl -G https://xquik.com/api/v1/x/bookmarks \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq # Page 2 curl -G https://xquik.com/api/v1/x/bookmarks \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const bookmarkFolderId = ""; // Set to a folder ID to export one folder. const baseUrl = "https://xquik.com/api/v1/x/bookmarks"; let pageCursor = ""; for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { const params = new URLSearchParams(); if (bookmarkFolderId !== "") params.set("folderId", bookmarkFolderId); if (pageCursor !== "") params.set("cursor", pageCursor); const query = params.toString(); const response = await fetch(query === "" ? baseUrl : `${baseUrl}?${query}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const bookmarkRows = page.tweets.map((tweet) => ({ bookmark_source: bookmarkFolderId === "" ? "all_bookmarks" : "folder", folder_id: bookmarkFolderId || null, tweet_id: tweet.id, tweet_url: tweet.url ?? null, 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), page_index: pageIndex, page_cursor: pageCursor, next_cursor: page.next_cursor, has_next_page: page.has_next_page, })); for (const row of bookmarkRows) process.stdout.write(`${JSON.stringify(row)}\n`); if (!page.has_next_page || page.next_cursor === "") break; pageCursor = page.next_cursor; } ``` ```python Python theme={null} import json import requests bookmark_folder_id = "" # Set to a folder ID to export one folder. page_cursor = "" for page_index in range(3): params = {} if bookmark_folder_id: params["folderId"] = bookmark_folder_id if page_cursor: params["cursor"] = page_cursor response = requests.get( "https://xquik.com/api/v1/x/bookmarks", params=params, headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) page = response.json() if not response.ok: raise RuntimeError(page) for tweet in page["tweets"]: bookmark_row = { "bookmark_source": "all_bookmarks" if not bookmark_folder_id else "folder", "folder_id": bookmark_folder_id or None, "tweet_id": tweet["id"], "tweet_url": tweet.get("url"), "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"), "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(bookmark_row, separators=(",", ":"))) if not page["has_next_page"] or not page["next_cursor"]: break page_cursor = page["next_cursor"] ``` ## Bookmarks handoff Use `GET /x/bookmarks` when a reading list, CRM, research queue, or agent needs saved tweets from the authenticated account. The examples write JSON Lines rows with bookmark source, folder ID, tweet ID, tweet URL, text, author ID, username, display name, follower count, verified state, profile image URL, engagement counts, media URLs, and cursor fields. Store the last saved `next_cursor` per folder. Resume each bookmark export from that checkpoint. Omit `folderId` when the workflow needs every bookmarked tweet visible to the connected account. Call [Bookmark Folders](/api-reference/x/bookmark-folders) first, then pass its `folder_id` as `folderId`. Store `has_next_page` and `next_cursor` per folder. Pass `next_cursor` back as `cursor` only when `has_next_page` is true. Keep saved-tweet rows in account-scoped research, CRM, or agent memory systems. ## Twitter Bookmark API Questions ### How Do I Export Twitter Bookmarks? Run the Node.js or Python example for each cursor page. Both examples create a structured JSON Lines record for every saved tweet. Each row keeps tweet text, authors, likes, replies, reposts, views, media URLs, and folder context. Convert the completed rows to a CSV file after collection. Use `tweet_id` as the stable key. Create a bookmarks page in a spreadsheet or research tool. Choose destination columns before sending rows to Notion databases or CRMs. ### How Does Bookmark Authentication Work? Connect the X account that owns the saved tweets. Include that account's Xquik API key in every request. The endpoint never accepts another username. This matches the Twitter developer privacy model. ### Can I Export Another User's Bookmarks? No. Bookmarks are private to the connected account. Keep every export scoped to that account. Do not publish saved tweets or private folder names. ### How Do Bookmark Folders Work? Call [Get bookmark folders](/api-reference/x/bookmark-folders) first. Pass the returned `folder_id` as `folderId`. Omit `folderId` to request all visible bookmarks from the connected account. Store one `next_cursor` checkpoint per folder. Preserve `folder_id` when importing records into Notion databases. ### What Limits a Bookmark Export? You receive the bookmarks that the connected account can view. Low credits may reduce the returned row count. A `429` response includes retry guidance. A `424` or `502` response means the read service failed temporarily. ### How Should I Store a Bookmark Export? Store the connected account ID beside each row. Keep private folder names out of public logs. Delete temporary files after the approved handoff finishes. ## Query parameters Bookmark folder ID. Omit to return all bookmarks. Pass the previous response's `next_cursor` to fetch another page. Omit it on the initial request. ## Which saved-feed endpoint? Use `GET /x/bookmarks` for bookmarked tweets from the connected account. Use [`GET /x/bookmarks/folders`](/api-reference/x/bookmark-folders) to find folder IDs before a folder-specific bookmark export. Use [`GET /x/timeline`](/api-reference/x/timeline) for the connected account's home feed instead of saved tweets. Use [`GET /x/notifications`](/api-reference/x/notifications) for inbox activity rows from the connected account. ## Headers Your API key. Session cookie authentication is also supported. ## Response ### 200 OK Array of bookmarked tweets. **Tweet object fields:** This value contains the tweet ID. This value contains the tweet text. This value identifies the tweet type. X may omit it. This value contains the ISO 8601 creation time. X may omit it. X sets this flag for long-form Note Tweets. X may omit it. This value records the like count. X may omit it. This value records the repost count. X may omit it. This value records the reply count. X may omit it. This value records the quote count. X may omit it. This value records the view count. X may omit it. This value records the bookmark count. X may omit it. This value contains the tweet URL. X may omit it. This value contains the tweet language code. X may omit it. X sets this flag for replies. X may omit it. This value identifies the replied-to tweet. X may omit it. This value identifies the replied-to user. X may omit it. This value identifies the replied-to username. X may omit it. This value contains the conversation ID. X may omit it. This value identifies the posting client. X may omit it. This array contains the rendered text offsets. X may omit it. X sets this flag when replies are limited. X may omit it. X sets this flag for quote tweets. X may omit it. This object contains parsed tweet entities. X may omit it. This object contains paid partnership and AI media labels. X may omit it. This object contains the tweet author profile. X may omit it. **Author object fields:** Author user ID. Author X username. Author display name. This value records the follower count. X may omit it. X sets this flag for verified authors. X may omit it. This value contains the profile picture URL. X may omit it. This array contains media attachments. X may omit it. **Media item fields:** Direct media URL. This array contains video URLs, bitrates, and content types. X omits it for images. Media type. Shortened URL from the tweet text. This object contains the quoted tweet. X may omit it. This object contains the original repost. X may omit it. Whether more results are available. Opaque cursor for the next page. Empty string when no more results. **Related:** [Bookmark Folders](/api-reference/x/bookmark-folders) · [Timeline](/api-reference/x/timeline) # Twitter Follower Checker API for X Accounts Source: https://docs.xquik.com/api-reference/x/check-follower GET /x/followers/check Check whether one X user follows another in either direction for giveaway eligibility, campaign proof, CRM flags, and relationship audits. See fields. ```json theme={null} { "isFollowing": true, "isFollowedBy": false, "sourceUsername": "elonmusk", "targetUsername": "jack" } ``` ```json theme={null} { "error": "missing_params" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Use this Twitter follower checker API to verify one relationship in either direction. Supply 2 usernames and store both boolean results. **5 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Direct [MPP](/mpp/machine-payments-protocol): USD 0.00075 per call Check follower verifies one known relationship without exporting a follower list. Pass the participant as `source` and the required brand, creator, or partner account as `target`. Each input accepts a username, `@username`, or supported X or Twitter profile URL. Xquik resolves profile URLs and converts both usernames to lowercase before lookup. The response returns both directions: `isFollowing` for source-to-target proof and `isFollowedBy` for target-to-source context. ```bash Follow task theme={null} curl -G https://xquik.com/api/v1/x/followers/check \ --data-urlencode "source=participant_handle" \ --data-urlencode "target=brand_handle" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} async function buildFollowCheckAudit() { const campaignId = "spring-launch-2026"; const participantHandle = "participant_handle"; const requiredFollowHandle = "brand_handle"; const params = new URLSearchParams({ source: participantHandle, target: requiredFollowHandle, }); const response = await fetch(`https://xquik.com/api/v1/x/followers/check?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); if (!response.ok) { throw new Error(`Follow check failed with status ${response.status}`); } const data = await response.json(); return { campaign_id: campaignId, participant_handle: data.sourceUsername, required_follow_handle: data.targetUsername, proof_endpoint: "GET /api/v1/x/followers/check", participant_follows_required_account: data.isFollowing, required_account_follows_participant: data.isFollowedBy, verification_state: data.isFollowing ? "matched" : "not_matched", }; } const auditEvent = await buildFollowCheckAudit(); // Replace this with your DB, queue, or warehouse write. await saveAuditEvent(auditEvent); ``` ```python Python theme={null} import requests def build_follow_check_audit(): campaign_id = "spring-launch-2026" participant_handle = "participant_handle" required_follow_handle = "brand_handle" response = requests.get( "https://xquik.com/api/v1/x/followers/check", params={ "source": participant_handle, "target": required_follow_handle, }, headers={"x-api-key": "xq_your_api_key_here"}, ) response.raise_for_status() data = response.json() return { "campaign_id": campaign_id, "participant_handle": data["sourceUsername"], "required_follow_handle": data["targetUsername"], "proof_endpoint": "GET /api/v1/x/followers/check", "participant_follows_required_account": data["isFollowing"], "required_account_follows_participant": data["isFollowedBy"], "verification_state": "matched" if data["isFollowing"] else "not_matched", } audit_event = build_follow_check_audit() # Replace this with your DB, queue, or warehouse write. save_audit_event(audit_event) ``` The Node.js and Python snippets build a campaign audit event instead of printing the raw response page. Store the event with your campaign, entrant, or CRM row so reviewers can see the proof endpoint, the two handles checked, and the matched or not-matched state. ## Campaign follow-check handoff Use `GET /api/v1/x/followers/check` when a workflow already has both usernames, `@usernames`, or supported profile URLs and needs one proof for a follow task. Use it for campaign entry validation, giveaway eligibility, creator partnerships, CRM qualification, and agent review queues. Store one audit event per participant and required account pair. Store `isFollowing` as the required proof and `isFollowedBy` as reciprocal context. Pass a username, `@username`, or supported X or Twitter profile URL. Numeric user IDs are not accepted. Use [Get user](/api-reference/x/twitter-profile-lookup) first when you only have a numeric ID. Persist the campaign ID, participant handle, required follow handle, endpoint, result booleans, and verification state. Use [Create draw](/api-reference/draws/create) when winner selection also needs reply, repost, keyword, or unique-author filters. Treat `402 insufficient_credits` as a stopped audit and resume after credits are available. ## Query parameters Source username, `@username`, or supported X or Twitter profile URL. Xquik resolves profile URLs and converts the username to lowercase. In campaign verification, this is usually the participant or entrant. Target username, `@username`, or supported X or Twitter profile URL. Xquik resolves profile URLs and converts the username to lowercase. In campaign verification, this is usually the required brand, creator, or partner account. ## Which verification endpoint? Use `GET /x/followers/check` for one participant-account follow proof. Use [`GET /x/tweets/{id}/retweeters`](/api-reference/x/retweeters) to page accounts that reposted one source tweet. Use [`GET /x/tweets/{id}/replies`](/api-reference/x/tweet-replies) to check public replies under the source tweet. Use [`GET /x/tweets/{id}/quotes`](/api-reference/x/tweet-quotes) to inspect quote-tweet entries. Use [`GET /x/users/{id}/followers`](/api-reference/x/followers) or a saved follower export when you need many followers for one profile. Use [`POST /draws`](/api-reference/draws/create) when Xquik should apply follow, repost, reply, keyword, and winner rules together. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` authenticates `paid_reads` guest keys. Direct MPP uses the `Payment ...` credential. Get it from the `WWW-Authenticate: Payment` challenge. ## Response ### 200 OK Canonical lowercase username resolved from `source`. Canonical lowercase username resolved from `target`. `true` if the source user follows the target user. `true` if the target user follows the source user. ```json theme={null} { "sourceUsername": "participant_handle", "targetUsername": "brand_handle", "isFollowing": true, "isFollowedBy": false } ``` ### 400 Invalid params ```json theme={null} { "error": "missing_params", "message": "Both source and target usernames are required.", "required": ["source", "target"] } ``` One or both query parameters are missing. Provide both `source` and `target`. ```json theme={null} { "error": "invalid_username", "message": "Invalid source username. Use username, @username, or an X or Twitter profile URL.", "parameter": "source" } ``` The named parameter is invalid. Use a username, `@username`, or supported X or Twitter profile URL. Numeric IDs, foreign hosts, profile status URLs, credentials, and custom ports are rejected. ### 401 Unauthenticated ```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. Anonymous calls receive a direct MPP `WWW-Authenticate: Payment` challenge plus a guest wallet creation action. 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 } ``` Your tier rate limit was exceeded. 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. **Next steps:** [Campaign verification workflow](/guides/campaign-verification-workflow) for audit rows and draw handoffs, [Get User](/api-reference/x/twitter-profile-lookup) to resolve profile details before checking, or [Get Account](/api-reference/account/get) to check remaining credits. # X Community Details API for Members & Rules Source: https://docs.xquik.com/api-reference/x/community-info GET /x/communities/{id}/info Retrieve an X community's name, description, member count, creator, moderators, rules, join policy, banner, and creation time. Includes request fields. ```json theme={null} { "community": { "id": "1500000000000000000", "name": "Tesla Fans", "description": "A community for Tesla enthusiasts", "banner_url": "https://xquik.com/example", "created_at": "2025-01-15T12:00:00Z" } } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Use the X communities API to resolve one community. Then read its members, moderators, rules, or tweets. Keep its numeric ID for later requests. ## Validate an X Community Before Extraction Check community details for every numeric community ID. Confirm the community name, description, creator, join policy, rules, and banner before requesting members or tweets. This prevents exports from using the wrong X community. Keep the returned community ID as a string. Store the creator and moderator details separately from the member count. Membership totals can change while the community identity remains stable. Use the rules and join policy to give analysts the correct context. They describe how the Twitter community operates. They do not replace member rows, moderator profiles, or community tweets. After validation, choose the smallest matching route. Fetch member profiles for audience review. Fetch moderators for governance review. Fetch recent tweets to review posts and engagement. Use saved extraction jobs to produce CSV, JSON, or XLSX output. ## Choose the Right Twitter Community API Start with community details when the numeric ID needs validation. Then choose the route that matches the records you need. Get the name, description, creator, rules, policies, and banner. Keep member and moderator counts with the record. Retrieve member profiles with usernames, bios, verification, follower counts, and cursor pagination. Retrieve recent tweets, authors, replies, reposts, likes, quotes, media, and cursor pages. Search one community for matching tweets. Keep the query and cursor with each page. Retrieve the smaller moderator roster without treating every member as a moderator. Run a community extraction to save CSV, JSON, or XLSX output. Use member routes for profile rows. Use tweet routes for posts and engagement counts. Use extraction jobs to save CSV, JSON, or XLSX output. ## Choose Community Metadata Use this route for community metadata, rules, policies, and counts. It returns one community record, not member profiles or tweet rows. Use member and tweet routes for those collections. **1 credit per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Direct [MPP](/mpp/machine-payments-protocol): USD 0.00015 per call ```bash cURL theme={null} curl https://xquik.com/api/v1/x/communities/1234567890/info \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const communityId = "1234567890"; const response = await fetch(`https://xquik.com/api/v1/x/communities/${communityId}/info`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const community = data.community; const communityRecord = { community_id: community.id, community_name: community.name ?? null, description: community.description ?? null, member_count: community.member_count ?? null, moderator_count: community.moderator_count ?? null, join_policy: community.join_policy ?? null, invites_policy: community.invites_policy ?? null, is_nsfw: community.is_nsfw ?? null, creator_id: community.creator?.id ?? null, creator_username: community.creator?.username ?? null, banner_url: community.banner_url ?? null, created_at: community.created_at ?? null, primary_topic_name: community.primary_topic?.name ?? null, rule_count: community.rules?.length ?? 0, }; process.stdout.write(JSON.stringify(communityRecord, null, 2)); ``` ```python Python theme={null} import json import requests response = requests.get( "https://xquik.com/api/v1/x/communities/1234567890/info", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() community = data["community"] community_record = { "community_id": community["id"], "community_name": community.get("name"), "description": community.get("description"), "member_count": community.get("member_count"), "moderator_count": community.get("moderator_count"), "join_policy": community.get("join_policy"), "invites_policy": community.get("invites_policy"), "is_nsfw": community.get("is_nsfw"), "creator_id": (community.get("creator") or {}).get("id"), "creator_username": (community.get("creator") or {}).get("username"), "banner_url": community.get("banner_url"), "created_at": community.get("created_at"), "primary_topic_name": (community.get("primary_topic") or {}).get("name"), "rule_count": len(community.get("rules") or []), } print(json.dumps(community_record, indent=2)) ``` Use `GET /x/communities/{id}/info` when a workflow needs one durable community profile row before member exports, moderator review, content routing, or CRM enrichment. Store `community_id`, `community_name`, `description`, `member_count`, `moderator_count`, policy fields, creator, `banner_url`, `created_at`, `primary_topic_name`, and `rule_count`. ## Qualify a Community Before Collecting Profiles or Tweets Read community metadata before starting a member or tweet export. This check prevents a copied community ID from sending results into the wrong project. Confirm `community.id` and `community.name` first. Save both values with the planned member or tweet export. Names can change, while the numeric ID anchors later member and tweet pages. Review `description` and `primary_topic` for research relevance. Keep this classification separate from tweet text. Community metadata describes the space, while tweet routes return individual posts. Check `join_policy` and `invites_policy` before planning member collection. Do not infer either policy from member counts. Store the returned values exactly, including missing values. Use `member_count` to estimate roster size. Use `moderator_count` only for staffing context. Neither count replaces the paginated member or moderator routes. Review `is_nsfw` before sharing banners, descriptions, or tweets. Follow the receiving system's content rules. Keep API responses unchanged. Capture every community rule with its ID, name, and description. Preserve the returned order. Do not merge several rules into one undocumented summary. Finish the qualification record with these decisions: * Continue to member profiles, moderator profiles, tweets, or keyword search. * Stop because the community ID or subject is incorrect. * Require a reviewer before handling sensitive community content. * Refresh metadata later because a required field is unavailable. This route qualifies the community. Collection routes return member profiles and community tweets. ## Create a Community Qualification Manifest Create one manifest before starting member, moderator, or tweet collection. Use the returned community ID as its stable key. Record these community facts: * Record the community name and description. * Record member and moderator counts. * Record join and invitation policies. * Record primary topic and content-sensitivity state. * Record the creator profile and banner URL when returned. * Record every rule ID, name, and description. Add a retrieval timestamp in UTC. Expect community names, counts, rules, and policies to change. The timestamp explains which metadata guided later collection. Keep planned collection work in a separate manifest section. Name the exact routes for members, moderators, unfiltered tweets, or keyword matches. Include the intended page size, search query, and output format when applicable. Keep member profiles and tweets in their own exports. Link those exports by community ID and manifest ID. Stop when the returned community ID differs. Require a review when the name or topic conflicts with the project brief. Record the decision instead of silently selecting another community. ## Use Community Metadata to Choose the Next Route The member route returns paginated profile rows. See [Community Members](/api-reference/x/community-members). The member count only estimates the expected roster size. Use [Community Moderators](/api-reference/x/community-moderators) for moderator profiles. The moderator count does not expose those usernames or user IDs. Use [Community Tweets](/api-reference/x/community-tweets) for the unfiltered community timeline. Preserve community ID and cursor with every page. Use [Community Search](/api-reference/x/community-search) for matching tweets. Preserve the exact keyword expression and sort mode with every result. Keep the creator object for ownership context. Require route evidence before exporting that profile as a member or moderator. ## Detect Community Metadata Drift Compare manifests by stable community ID. Never join them by community name. Report name, description, topic, policy, rule, and sensitivity changes separately. A member-count change does not prove a policy change. Track community rules and tweets independently. Store both retrieval times and both returned values. Preserve absent fields. Never invent policy or count defaults. Refresh this endpoint before long-running exports. Attach the newest manifest ID to every new collection job. Keep earlier manifests immutable for audits. ## Path parameters Community ID (numeric string). ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` authenticates `paid_reads` guest keys. Direct MPP uses the `Payment ...` credential. Get it from the `WWW-Authenticate: Payment` challenge. ## Response ### 200 OK Community details. **Community object fields:** Community ID. Community name. Omitted if unavailable. Community description. Omitted if empty. Total member count. Omitted if unavailable. Total moderators. Omitted if unavailable. Join policy (e.g. `Open`). Omitted if unavailable. Invitation policy. Omitted if unavailable. Whether the community is marked sensitive. Omitted if unavailable. Creator profile with `id`, `username`, `verified`, and optional `name`. Omitted if unavailable. Banner image URL. Omitted if unavailable. ISO 8601 creation timestamp. Omitted if unavailable. Primary topic with `id` and `name` fields. Omitted if unavailable. Community rules, each with `id`, `name`, and `description`. Omitted if unavailable. ```json theme={null} { "community": { "id": "1234567890", "name": "Web Developers", "description": "A community for web developers", "member_count": 15000, "moderator_count": 5, "join_policy": "Open", "created_at": "2024-01-15T00:00:00.000Z", "primary_topic": { "id": "1", "name": "Technology" }, "rules": [ { "id": "1", "name": "Be respectful", "description": "Treat all members with respect." } ] } } ``` ### 400 Invalid community ID ```json theme={null} { "error": "invalid_community_id" } ``` The community ID is empty or invalid. ### 401 Unauthenticated ```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. Anonymous calls get a direct MPP `WWW-Authenticate: Payment` challenge. They also get a guest wallet creation action. No checkout starts automatically. Confirm any payment action. ### 404 Community not found ```json theme={null} { "error": "not_found" } ``` The community could not be resolved. Check the community ID. ### 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 } ``` Your tier rate limit was exceeded. Wait for the `Retry-After` header before retrying. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` Opted-in normalized v1 calls return 424 when the read service fails. **Next steps:** [Community Members](/api-reference/x/community-members) to list members, or [Community Tweets](/api-reference/x/community-tweets) to browse posts. # X Community Members & Profile Export API Guide Source: https://docs.xquik.com/api-reference/x/community-members GET /x/communities/{id}/members Scrape X community members into profile rows with usernames, bios, verification, and follower counts. Export every cursor page for review or CRM imports. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Scrape X community members into explicit profile rows. Store the community and user IDs with every cursor result. Keep the username, bio, verification, follower count, and cursor. ## X Community Member Scraping Questions ### Scrape X Community Members Call `GET /api/v1/x/communities/{id}/members` with the numeric community ID. Store the community ID and stable user ID. Keep the username, profile name, bio, location, and verification. Record an ISO 8601 collection time before requesting another cursor. Store available follower and following counts. Keep the profile image URL. Store the cursor returned with each profile batch. Save the page before advancing its opaque cursor. Use `has_next_page` as the only pagination guard. Send another request only for the boolean `true`. Reuse the exact `next_cursor`. Deduplicate a resumed page by community ID and user ID. Mark credit-bounded, failed, or interrupted runs as partial. Use the moderators route for role-specific profiles. The members route returns every visible member profile. ### What Is the Best Way to Extract Data From a Twitter Community? Replace “data” with the community records the workflow needs. Use the info route for the community name and description. Use the members route for user IDs and profile fields. Use the moderators route for role-specific profiles. Retrieve recent Community posts through the tweets route. Search Community posts for a keyword through Community Search. Keep community profiles and tweets in separate tables. Join them with stable community and user IDs. Save page cursors, collection times, row counts, and completion states. Export JSON for applications. Create CSV or XLSX only when analysts need rows. Never flatten membership, moderator roles, and tweet activity into one ambiguous record. ### How Do I Scrape Members From an X Community? Call `GET /api/v1/x/communities/{id}/members` with the numeric community ID. Store each user ID, username, profile name, bio, and verification state. Add the follower count and page cursor. Stop when `has_next_page` is false. Until then, send `next_cursor` without modification. Save each profile page before advancing its cursor. Deduplicate resumed pages by stable user ID. Write the community ID into every exported profile record. Record the collection time because usernames, bios, verification, and audience counts can change. Do not infer moderator status from membership. Use the dedicated moderators route for that role. Mark an interrupted or credit-bounded export as partial. ### Twitter Community API Choose the route that matches the record. The info route returns community details. The members route returns member profiles. The moderators route returns moderator profiles. The tweets route returns recent community posts. Community search returns posts matching a query. For member exports, build one roster row per community ID and user ID. Keep profile fields, source cursor, collection time, and completion state. For tweet exports, store Tweet ID, author ID, text, timestamp, engagement, media, and cursor. Keep those schemas separate. Use stable IDs for joins. Respect API-key scope, credits, page limits, and rate limits. ### How Should I Compare Community Member Lists? Complete every cursor page before comparing snapshots. Mark interrupted runs as partial. Use community ID plus user ID as the comparison key. Report newly observed and missing members separately. Never assign a reason for an addition or removal. Refresh a profile only when later work requires current follower counts, following counts, or biographies. Requested result counts are upper bounds for paid authenticated calls. Low credit balances can reduce a page or ID list. If zero results are affordable, Xquik returns `402 insufficient_credits`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl https://xquik.com/api/v1/x/communities/1234567890/members \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const communityId = "1234567890"; const response = await fetch(`https://xquik.com/api/v1/x/communities/${communityId}/members`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const nextCursor = data.has_next_page ? data.next_cursor : null; const memberRows = data.users.map((user) => ({ community_id: communityId, member_id: user.id, username: user.username, display_name: user.name, bio: user.description ?? null, follower_count: user.followers ?? null, verified: user.verified ?? false, profile_image_url: user.profilePicture ?? null, next_cursor: nextCursor, })); process.stdout.write(JSON.stringify(memberRows, null, 2)); ``` ```python Python theme={null} import json import requests community_id = "1234567890" response = requests.get( f"https://xquik.com/api/v1/x/communities/{community_id}/members", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() next_cursor = data["next_cursor"] if data["has_next_page"] else None member_rows = [ { "community_id": community_id, "member_id": user["id"], "username": user["username"], "display_name": user["name"], "bio": user.get("description"), "follower_count": user.get("followers"), "verified": user.get("verified", False), "profile_image_url": user.get("profilePicture"), "next_cursor": next_cursor, } for user in data["users"] ] print(json.dumps(member_rows, indent=2)) ``` Use `GET /x/communities/{id}/members` for member exports, CRM enrichment, audience review, or moderator handoff. It creates one row per community member. Store `community_id`, `member_id`, `username`, `display_name`, `bio`, `follower_count`, `verified`, `profile_image_url`, and `next_cursor`. ## Build a Community Member Export The community member export becomes complete after every cursor page finishes. Save each profile page before advancing its cursor. Mark credit-bounded, failed, and interrupted runs as partial membership snapshots. Mark the export complete only after `has_next_page` is `false`. Community membership shows roster inclusion. It does not prove posting activity. Retrieve recent Community posts through [Community Tweets](/api-reference/x/community-tweets). Use [Community Search](/api-reference/x/community-search) to find posts by keyword. Join those posts to member profiles by `manifest_id`, `community_id`, and stable user ID. Use [Community Moderators](/api-reference/x/community-moderators) for role-specific profiles. Verification and follower counts do not prove a moderator role. X Lists use different IDs and membership rules. Review X's official [List Members Lookup](https://docs.x.com/x-api/lists/list-members/quickstart/list-members-lookup) before migrating a List workflow. Store `source_type` and its source ID. Use `community_id` for Community rows and `list_id` for List rows. Join and deduplicate with the source ID plus `member_id`. Use this route to enumerate profiles that belong to one community. Keep the community ID on every exported row. A username alone cannot identify its source community. Choose columns that support the next task: * User ID, username, and profile name. * Biography, location, and verification state. * Follower and following counts. * Store the Community ID and cursor with every page's ISO 8601 collection time. Use this Community member roster for directories, CRM enrichment, or moderator review. Do not label every member as a moderator. Use the moderators route to retrieve only moderator profiles. Treat `has_next_page` as the only pagination guard. The next request requires the boolean value `true`. Pass `next_cursor` without modification. Persist each page before saving its cursor. Deduplicate repeated results by user ID after resuming a job. Profile fields can change after collection. Record an ISO 8601 time for every returned profile. Refresh the profile through user lookup when the workflow requires current bios, follower counts, following counts, or avatars. ## Compare Community Membership Snapshots Collect every cursor page before comparing membership. Mark interrupted runs as partial. Use community ID and user ID as the comparison key. Record newly observed and missing members separately. Avoid inferring why a membership changed. Keep the snapshot’s page count, row count, first cursor, and completion time. These values help reviewers distinguish a real change from an incomplete export. Store membership notes outside returned profile fields. Never place private moderation notes inside a public username or biography column. When another process adds moderator status, store it in a separate `community_role` column. ## Path Parameters Community ID (numeric string). ## Query Parameters Pagination cursor from a previous response. Omit for the initial request. Results per request. Range: 20-200. Default: `20`. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of community members. **User object fields:** User ID. X username. Display name. Profile bio. Reports the profile's follower count. Following count. Verified status. Profile image URL. Profile location. Account creation date (ISO 8601). Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines. Omitted if unavailable. Whether the user is an X translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. Lists pinned Tweet IDs. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable. Omitted if available. Reason the account is unavailable. Omitted if available. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Indicates whether the account protects its Tweets. Omitted if unavailable. Role within the requested community context. Omitted outside community results. Whether more results are available. Cursor for subsequent results. Pass as the `cursor` query parameter. ```json theme={null} { "users": [ { "id": "987654321", "username": "username", "name": "Xquik", "followers": 10000, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/xquik/photo.jpg" } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid community ID ```json theme={null} { "error": "invalid_community_id" } ``` The community ID is empty or invalid. ### 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. ### 404 Community not found ```json theme={null} { "error": "not_found" } ``` The community could not be resolved. Check the community ID. ### 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 } ``` Your tier rate limit was exceeded. Wait for the `Retry-After` header before retrying. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` Send `xquik-api-contract: 2026-04-29` to opt in to HTTP 424. Default v1 returns HTTP 502 with `x_api_unavailable`. **Next steps:** [Community Info](/api-reference/x/community-info) for community details, or [Community Moderators](/api-reference/x/community-moderators) to list moderators. # X Community Moderators API & Admin Profiles Source: https://docs.xquik.com/api-reference/x/community-moderators GET /x/communities/{id}/moderators Retrieve X community moderators with usernames, bios, verification state, profile media, and follower and following counts. Includes costs and errors. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Export moderator profiles for one X community. Store stable user IDs for access reviews. Analyze visible profiles or monitor the moderator roster. ## X Community Moderator API Questions ### What Does an X Community Moderator Do? X's moderator playbook separates creators, admins, moderators, and members. The creator is also the first admin. Admins can change settings and manage moderator roles. Moderators review reported Community posts, manage members, and enforce Community rules. This route only reads profiles exposed by the visible moderator roster. It cannot assign roles, remove members, return reports, or expose private actions. Read X's official [Communities Moderator Playbook](https://help.x.com/en/using-x/communities-moderator-playbook) for current role responsibilities. Store `communityRole` only when the response supplies it. Never infer admin status from verification, follower counts, biographies, or profile images. ### How Do I Find X Community Moderators? Open the Community page when one manual lookup is enough. X says direct Community URLs expose visible member and moderator lists. X documents that public interface in its official [Communities guide](https://help.x.com/en/using-x/communities). Use this API for repeatable collection, pagination, exports, and snapshot review. Start with the numeric Community ID. Store that ID with every moderator ID. A username can change and cannot identify the source Community. Request another page only when `has_next_page` is exactly `true`. Pass the returned cursor string back without editing it. Mark the roster complete only after `has_next_page` is `false`. ### Which Tools Support X Community Moderation? Choose each endpoint by the record it returns: * Retrieve visible moderator profiles with this route. * Retrieve rules and counts with [Community Info](/api-reference/x/community-info). * Retrieve recent posts with [Community Tweets](/api-reference/x/community-tweets). * Search Community posts with [Community Search](/api-reference/x/community-search). * Review reports or change roles in X's own moderator interface. The moderator API does not return enforcement logs or reported-post queues. Keep those workflows separate from public profile exports. This boundary prevents a profile row from becoming an unsupported moderation decision. ### Can AI Automate Community Moderation? Do not automate enforcement from a moderator profile snapshot. Verification, follower counts, biographies, and avatars do not prove behavior or permissions. Use automation to normalize rows, compare complete snapshots, and queue human review. An AI model may summarize an approved public change set. Keep private review notes outside the exported profile record. Require a human decision before any account action. 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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl https://xquik.com/api/v1/x/communities/1234567890/moderators \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const communityId = "1234567890"; const response = await fetch(`https://xquik.com/api/v1/x/communities/${communityId}/moderators`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const nextCursor = data.has_next_page ? data.next_cursor : null; const moderatorRows = data.users.map((user) => ({ community_id: communityId, moderator_id: user.id, username: user.username, display_name: user.name, bio: user.description ?? null, follower_count: user.followers ?? null, verified: user.verified ?? false, profile_image_url: user.profilePicture ?? null, page_size: data.users.length, has_next_page: data.has_next_page, next_cursor: nextCursor, })); process.stdout.write(JSON.stringify(moderatorRows, null, 2)); ``` ```python Python theme={null} import json import requests community_id = "1234567890" response = requests.get( f"https://xquik.com/api/v1/x/communities/{community_id}/moderators", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() next_cursor = data["next_cursor"] if data["has_next_page"] else None moderator_rows = [ { "community_id": community_id, "moderator_id": user["id"], "username": user["username"], "display_name": user["name"], "bio": user.get("description"), "follower_count": user.get("followers"), "verified": user.get("verified", False), "profile_image_url": user.get("profilePicture"), "page_size": len(data["users"]), "has_next_page": data["has_next_page"], "next_cursor": next_cursor, } for user in data["users"] ] print(json.dumps(moderator_rows, indent=2)) ``` Use `GET /x/communities/{id}/moderators` for moderator audits, governance review, trust and safety queues, or CRM enrichment. It creates one row per community moderator. Store `community_id`, `moderator_id`, `username`, `display_name`, `bio`, `follower_count`, `verified`, `profile_image_url`, and `next_cursor`. Store `page_size` and `has_next_page` with the checkpoint when you paginate moderator audits or saved review queues. ## Direct Moderator Handoff Use the first page with no `cursor`. Request the next page only when `has_next_page` is exactly `true`. Pass `next_cursor` back as `cursor` without modification. Requested result counts are upper bounds. Use `page_size` to record the returned moderator count for each page. Store one row per moderator with community ID, user ID, username, profile fields, verification, and follower count. Persist each page and its pagination fields before requesting another page. Expect up to the default page size per call, reduced when the caller cannot cover every paid result. Use `community_moderator_explorer` when the workflow needs an extraction job or CSV, JSON, or XLSX output. ## Build a Complete Moderator Roster Treat each response page as a durable checkpoint. Save every profile row before storing its cursor. Resume with the exact saved cursor after an interruption. Create a local `export_manifest_id` for each collection run. Store it with `community_id`, `moderator_id`, page number, cursor, and collection time. This key prevents one roster from mixing with another collection run. Label credit-bounded, failed, and interrupted runs as incomplete moderator exports. Do not compare a partial roster with a completed baseline. A partial comparison can falsely report removed moderators. Deduplicate resumed pages with `community_id` and `moderator_id`. Keep the latest username as a label. Use numeric user IDs to join collection runs. Store these completion fields with the export manifest: * Requested Community ID. * Returned page count and profile count. * First and final cursor values. * Final `has_next_page` value. * Collection start and completion times. * Complete, partial, or failed status. These fields show whether the roster finished successfully. ## Audit a Community Moderation Team Use this route when the role matters more than general membership. Every returned profile represents a moderator visible for the selected community. Keep the community ID and collection time with each profile. Review moderator rows for: * User ID, username, and profile name. * Verification state and public biography. * Follower counts and profile image. * Cursor position and collection time. Compare snapshots by user ID. A changed username does not represent a new moderator. Flag added and removed IDs for review. Do not combine moderator rows with the full member roster silently. Give each export a clear role column. This prevents downstream tools from granting moderator meaning to ordinary members. Both `community_id` and your local `export_manifest_id` must match. Then join the moderator roster with [Community Info](/api-reference/x/community-info). Keep rules, member counts, and moderator profiles in separate tables. This preserves each endpoint's public response contract. Store `communityRole` only when the response provides it. Never expand it into a broader permission. An omitted role cannot prove admin, creator, or member permissions. ## Review Moderator Coverage Compare complete moderator snapshots by Community ID and user ID. Record added and removed IDs without guessing the reason. Use this review sequence: 1. Confirm both exports ended with `has_next_page` set to `false`. 2. Confirm both exports use the same numeric Community ID. 3. Compare stable moderator IDs, not usernames or display names. 4. Ask a reviewer to inspect added and missing IDs. 5. Store the decision outside the public profile snapshot. Keep one row per visible moderator. Store the username only as a mutable label. Use the numeric user ID for durable joins. Escalate unexpected changes to a human reviewer. Require human approval before any account action. Store each review outcome outside the public profile snapshot. Record the reviewer, decision time, and Community ID in your own system. Refresh a profile only when current biography or counts matter. Preserve the original moderator observation. ## Path Parameters Community ID (numeric string). ## Query Parameters Pagination cursor from a previous response. Omit for the first page. ## Which Community Endpoint? Use `GET /x/communities/{id}/moderators` for governance audits, moderator review queues, and profile enrichment. Use [`GET /x/communities/{id}/members`](/api-reference/x/community-members) for the broader member list. Use [`GET /x/communities/{id}/info`](/api-reference/x/community-info) for member count, moderator count, rules, and join policy. Use [`GET /x/communities/{id}/tweets`](/api-reference/x/community-tweets) for posts inside one community. Use [`GET /x/communities/tweets`](/api-reference/x/community-search) for keyword search across community tweets. Use [`Create extraction`](/api-reference/extractions/create) with `community_moderator_explorer`, `community_extractor`, or `community_post_extractor` for queued file exports. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of community moderators. **User object fields:** User ID. X username. Display name. Profile bio. Reports the profile's follower count. Following count. Verified status. Profile image URL. Profile location. Account creation date (ISO 8601). Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines. Omitted if unavailable. Whether the user is an X translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. Lists pinned Tweet IDs. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable. Omitted if available. Reason the account is unavailable. Omitted if available. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. Role within the requested community context. Omitted outside community results. Whether more results are available. Cursor for the next page. Pass as the `cursor` query parameter. ```json theme={null} { "users": [ { "id": "987654321", "username": "moduser", "name": "Moderator", "followers": 5000, "verified": true } ], "has_next_page": false, "next_cursor": "" } ``` ### 400 Invalid community ID ```json theme={null} { "error": "invalid_community_id" } ``` The community ID is empty or invalid. ### 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. ### 404 Community not found ```json theme={null} { "error": "not_found" } ``` The community could not be resolved. Check the community ID. ### 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 } ``` Your tier rate limit was exceeded. Wait for the `Retry-After` header before retrying. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` Send `xquik-api-contract: 2026-04-29` to opt in to HTTP 424. Default v1 returns HTTP 502 with `x_api_unavailable`. **Next steps:** [Community Members](/api-reference/x/community-members) for the full member list, or [Community Info](/api-reference/x/community-info) for community details. # Twitter Community Search API & Keyword Tweet Results Source: https://docs.xquik.com/api-reference/x/community-search GET /x/communities/search Search posts inside one known X (Twitter) community by keyword. Export matching tweets, authors, replies, reposts, likes, media, and cursor pages for reviews. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Use Twitter community search to filter posts inside one known Community. Run a Twitter search in community posts with a numeric Community ID and query. Store Tweet IDs, authors, engagement counts, media, and cursors for exports. ## Filter One Community by Query This endpoint requires a search expression. It filters one community instead of returning the unfiltered feed. Keep the query beside every saved row. | Search decision | Request field | Result boundary | | --------------- | ----------------------------- | --------------------------------------------------- | | Community scope | `communityId` | Search only the selected community. | | Text filter | `q` | Return posts matching the exact search expression. | | Ranking mode | `queryType=Latest` | Build a recent moderation or monitoring queue. | | Ranking mode | `queryType=Top` | Build a relevance-ranked research set. | | Continuation | `cursor` | Resume the same community, query, and ranking mode. | | Saved export | `community_search` extraction | Produce CSV, JSON, or XLSX query results. | Use the community tweets endpoint when no keyword filter is required. ## Twitter Community Search Questions ### Does This Endpoint Find Communities to Join? No. This endpoint searches posts after you provide a numeric Community ID. It does not discover Communities, join them, or change membership. Use X's Communities interface to discover groups and confirm participation rules. Read the official [X Communities guide](https://help.x.com/en/using-x/communities) for current discovery, visibility, and membership behavior. ### How Do I Search Community Posts by Keyword? Send `communityId` and `q`. Omit `queryType` to use `Latest`. Set `Top` for relevance-ranked matches. Keep each cursor tied to the same values. Start a new search when the query changes. Store the query beside every returned Tweet ID. ### Why Does Twitter Community Search Return No Results? First, verify the Community ID, query, and read visibility. An empty match set differs from authentication, credit, dependency, rate-limit, or request errors. Use the [Community Tweets API](/api-reference/x/community-tweets) to inspect the visible, unfiltered feed. Zero matches do not prove an inactive Community. ### Can I Find Active Authors in Matching Tweets? Group matching posts by stable author ID. Count matches, replies, reposts, likes, quotes, and views separately. Store follower counts with collection times when returned. These measures describe captured matches. They do not prove influence, audience reach, Community membership, or total posting activity. `GET /x/communities/search` and `GET /x/communities/tweets` accept the same community search parameters. This page documents both supported REST paths. 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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl -G https://xquik.com/api/v1/x/communities/search \ --data-urlencode "communityId=1234567890" \ --data-urlencode "q=web development" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const communityId = "1234567890"; const searchQuery = "web development"; const queryType = "Latest"; const pageSize = "100"; const params = new URLSearchParams({ communityId, q: searchQuery, queryType, pageSize }); const response = await fetch(`https://xquik.com/api/v1/x/communities/search?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const tweetRows = data.tweets.map((tweet) => { const author = tweet.author ?? {}; return { community_id: communityId, search_query: searchQuery, query_type: queryType, 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, like_count: tweet.likeCount ?? null, reply_count: tweet.replyCount ?? null, retweet_count: tweet.retweetCount ?? null, media_urls: tweet.media?.map((item) => item.mediaUrl).filter(Boolean) ?? [], }; }); const nextCursor = data.has_next_page ? data.next_cursor : null; for (const row of tweetRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } if (nextCursor !== null) { const checkpoint = { community_id: communityId, search_query: searchQuery, query_type: queryType, page_size: Number(pageSize), next_cursor: nextCursor, }; process.stdout.write(`${JSON.stringify(checkpoint)}\n`); } ``` ```python Python theme={null} import json import requests community_id = "1234567890" search_query = "web development" query_type = "Latest" page_size = 100 response = requests.get( "https://xquik.com/api/v1/x/communities/search", params={ "communityId": community_id, "q": search_query, "queryType": query_type, "pageSize": page_size, }, headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() tweet_rows = [] for tweet in data["tweets"]: author = tweet.get("author") or {} tweet_rows.append( { "community_id": community_id, "search_query": search_query, "query_type": query_type, "tweet_id": tweet["id"], "text": tweet.get("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"), "like_count": tweet.get("likeCount"), "reply_count": tweet.get("replyCount"), "retweet_count": tweet.get("retweetCount"), "media_urls": [ item["mediaUrl"] for item in tweet.get("media", []) if item.get("mediaUrl") ], } ) next_cursor = data["next_cursor"] if data["has_next_page"] else None for row in tweet_rows: print(json.dumps(row)) if next_cursor is not None: print(json.dumps({ "community_id": community_id, "search_query": search_query, "query_type": query_type, "page_size": page_size, "next_cursor": next_cursor, })) ``` The Node.js & Python snippets shape one durable row per matching community tweet instead of printing the full response page. Persist the final `next_cursor` row when `has_next_page` is true, then pass it back as `cursor` with the same `communityId`, `q`, `queryType`, and `pageSize`. ## Direct Community Search Handoff Use `GET /x/communities/search` when a monitoring job, research queue, moderation review, social listening workflow, or agent needs matching tweets from one known X community. Store `community_id`, `search_query`, `query_type`, `tweet_id`, `text`, `author_id`, `author_username`, `author_name`, `author_followers`, `author_verified`, `author_profile_picture`, `created_at`, engagement counts, & `media_urls` for each row. Keep `has_next_page` & `next_cursor` with the export checkpoint so the next run can continue the same scoped search without duplicating earlier rows. Set `queryType=Latest` for recent queues or backfills. Set `queryType=Top` for relevance-ranked review. Store `community_id`, `search_query`, `query_type`, `page_size`, `has_next_page`, and `next_cursor` with the tweet rows. Use `Latest` for recent collection and `Top` for relevance-ranked review. Keep the same `queryType` when you pass a cursor. Request 1 to 100 tweets with `pageSize`. The default is 20. Treat the value as an upper bound because filters, source results, or credits can return fewer. Use `community_search` with `targetCommunityId` and `searchQuery` when the workflow needs a saved job with CSV/JSON/XLSX output. ## Plan a Community Search Export Choose this route for repeatable research across one known community. Define the question before choosing the query. A focused query produces cleaner tweet rows and simpler review. Start each export with these values: * The numeric community ID. * The exact search expression. * Either `Latest` or `Top`. * A stable page size. * The time when collection started. Keep those values beside every saved cursor. Resume with the same values. Changing the query during pagination creates a different result stream. Use `Latest` for incident review, event coverage, and recent topic monitoring. Use `Top` for relevance-ranked discovery. Do not combine both orders inside one export file. Create separate exports when reviewers need both perspectives. Normalize each tweet into explicit columns. Useful columns include tweet ID, text, author username, creation time, likes, replies, reposts, and media URLs. Keep the community ID and query on every row. Those columns preserve context after CSV or XLSX handoff. Stop when `has_next_page` becomes false. Store the final checkpoint with the row count. Deduplicate resumed exports by tweet ID. This protects downstream spreadsheets when a worker retries the last completed page. ## Write Precise Community Queries Use concrete terms that match the research question. Combine keywords with supported search operators when required. Test the first page before starting a large export. For moderation, search the specific phrase or hashtag under review. For research, separate broad themes into independent queries. For event coverage, record the chosen sort order and collection time. Avoid changing `q` after receiving a cursor. Start a new search instead. This keeps each cursor tied to one understandable result set. ## Validate a Completed Research File Count unique tweet IDs after the final page. Compare that count with the written row count. Any difference reveals repeated rows. Check that every row carries the same community ID, query, and sort mode. Reject a mixed file before analyst handoff. Keep media URLs as arrays or separate child rows. Write a short export manifest. Include collection time, page count, unique tweet count, final cursor state, and output format. This makes a CSV or XLSX file understandable without the original job logs. When a run stops early, label it partial. Preserve the last durable cursor for resumption. Do not present a partial export as the community’s complete search result. ## Schedule Independent Searches Give every community-and-query pair its own checkpoint. Never share cursors between two terms. Run urgent moderation searches more frequently than broad research queries. Record the schedule beside the export manifest. If a query changes, start a new series. This preserves understandable comparisons across collection windows. Use separate output names for each community. Include a short query slug and collection date. Keep the full query inside the manifest. Archive successful manifests beside their CSV, JSON, or XLSX files. This lets another analyst reproduce the search parameters without opening application logs. Version the manifest when a query changes. Keep earlier exports immutable. This preserves comparisons between research periods and prevents silent rewrites. ## Compare Latest and Top Results Without Mixing Datasets Run `Latest` and `Top` as independent searches when research needs both views. They answer different questions and may return overlapping tweets. Give each run its own manifest, cursor chain, and output file. Keep the same community ID and query when comparing the two modes. Changing another input would invalidate the comparison. Use `Latest` to capture recent discussion. Record when the first page was requested. New tweets can appear while later pages are collected. Use `Top` to capture relevance-ranked discussion. Record the collection time, but do not treat rank as a permanent score. The order can change later. After both runs finish, join rows by tweet ID. Label every tweet as `latest_only`, `top_only`, or `both`. Keep the original engagement counts from each run when collection times differ. Do not append one mode beneath the other without a source column. Analysts could mistake duplicated tweets for extra community activity. Validate each dataset before comparison: * Every row uses the intended community ID. * Every row stores the exact search query. * Every cursor belongs to one sort mode. * Duplicate tweet IDs are removed within each run. * Partial runs remain clearly labeled. Use the combined view for research prioritization. Preserve the independent exports for reproducibility and later audits. ## Query Parameters Numeric ID of the community whose tweets you want to search. Search query for community tweets. Sort order. `Top` returns most relevant tweets, `Latest` returns most recent. Defaults to `Latest`. Pagination cursor from a previous response. Omit for the first page. Upper bound for tweets per page. Range: 1-100. Default: `20`. ## Which Community Search Route? Use `GET /x/communities/search` with `communityId` and `q` for scoped search. Use `GET /x/communities/tweets` when your integration already uses that path. It accepts the same `communityId`, `q`, `queryType`, `cursor`, and `pageSize` shape. Use [`GET /x/communities/{id}/tweets`](/api-reference/x/community-tweets) for posts from one known community ID. Use [`Create extraction`](/api-reference/extractions/create) with `community_search` with `targetCommunityId` and `searchQuery` when the workflow needs a saved filtered export. Use `community_post_extractor` for all posts from a known community. ## Build a Live Community Review Queue Choose either documented path for direct, page-by-page tweet retrieval. Both paths work behind moderation screens, support consoles, and analyst dashboards. Show the active community ID, query, and sort mode above the results. Reviewers should always know why each tweet appeared. Render concrete tweet fields: * Tweet text, Tweet ID, and creation time. * Author name, username, user ID, and verification state. * Reply, repost, like, quote, and view counts when returned. * Attached photo, video, or animated GIF URLs. * A source URL for opening the original tweet. Keep `next_cursor` outside the visible tweet list. Bind it to the community, query, sort mode, and page size. Disable the next-page action during requests. Disable it permanently when `has_next_page` becomes false. Treat an empty page as a valid search result. It does not mean the community is missing. Display empty matches separately from authentication, credit, and request errors. ## Preserve Decisions Across a Live Moderation Queue Create one queue identity from the community ID, query, and sort mode. Keep it unchanged while reviewers page through matching tweets. Store each decision against `tweet.id`. Never attach labels to visible row numbers. New tweets can change result order between `Latest` requests. Separate four queue states: * Unreviewed tweets from the current cursor page. * Selected tweets awaiting an explicit action. * Reviewed tweets with a stored decision and review time. * Failed page requests that retain their previous cursor. When a page fails, keep the current rows and cursor. Retry with identical query parameters. Do not advance the cursor until every row is durable. Treat `Latest` as a moving queue. Deduplicate incoming rows by Tweet ID. Keep earlier decisions when the same tweet appears again. Treat `Top` as a relevance review. Record each collection time because ranking can change. Do not compare row positions across separate requests. Pass approved Tweet IDs into reply, thread, profile, or media workflows. Keep each follow-up response separate from the search result. Your queue owns the reviewer, decision, and review time fields. ## Build a Query-Specific Community Review Batch Save the exact query before requesting tweets. Preserve every operator, quoted phrase, exclusion, language choice, and engagement filter. Reviewers must see the expression that produced the queue. Store Tweet ID, author ID, text, creation time, engagement, and media for every match. Add the request time and returned cursor. Keep reviewer fields in a separate table keyed by Tweet ID. Assign one purpose to each batch. Examples include campaign replies, support complaints, product feedback, rule violations, or event coverage. Never mix unrelated queries inside one review queue. Persist each cursor page before requesting another. Deduplicate repeated Tweet IDs after retries. Never discard a prior decision when engagement counts change. Record one terminal state: completed, capped, credit-bounded, or interrupted. A live search page never proves a complete historical community archive. ## Separate Search Matches From Community Feed Coverage Community search returns tweets matching one expression. It does not return every recent post. Use the community feed route for an unfiltered timeline. Keep match counts separate from total community activity. A narrow query can return zero tweets while the community remains active. A broad query can create more review work without improving relevance. Test the expression before opening a long queue. Inspect several Tweet IDs, authors, timestamps, replies, reposts, likes, and media URLs. Narrow recurring false matches with supported operators. Compare two queries with the same time window and result cap. Count unique Tweet IDs for each expression. Report overlapping matches separately. Never combine both result sets under one unrecorded query label. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of matching community tweets. **Tweet object fields:** Tweet ID. Contains the complete Tweet text. Classifies the Tweet when X returns a type. ISO 8601 creation timestamp. Whether this is a Note Tweet. Omitted if unavailable. Reports the number of likes when available. Reports the number of reposts when available. Reports the number of replies when available. Reports the number of quotes when available. Reports the number of views when available. Reports the number of bookmarks when available. Permalink URL on X. Omitted if unavailable. Reports the Tweet language code when available. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. Identifies the replied-to user when available. Reports the replied-to username when available. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. Returns paid-promotion and AI-generated-media labels when available. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia`. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author X username. Author display name. Reports the author's follower count when available. Whether the author is verified. Omitted if unavailable. Profile picture URL. Omitted if unavailable. Lists media items attached to the Tweet. Omitted when none exist. **Media object fields:** Provides the direct media URL. Lists available video renditions and playback details. Omitted for images. Identifies the attached media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Cursor for the next page. Pass as the `cursor` query parameter. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "Great discussion about web development", "createdAt": "2026-02-24T10:00:00.000Z", "likeCount": 75, "retweetCount": 12, "replyCount": 8, "viewCount": 5400, "author": { "id": "987654321", "username": "devuser", "name": "Developer", "followers": 25000, "verified": false, "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg" } } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Missing query ```json theme={null} { "error": "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. ### 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 } ``` Your tier rate limit was exceeded. 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. **Next steps:** [Community Info](/api-reference/x/community-info) to look up a community, or [Search Tweets](/api-reference/x/search-tweets) for general tweet search. # Twitter Community Posts API & X Timeline Export Source: https://docs.xquik.com/api-reference/x/community-tweets GET /x/communities/{id}/tweets Export X community tweets with authors, text, replies, reposts, likes, quotes, and media. Traverse cursor pages or save CSV, JSON, and XLSX exports for reviews. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Export Twitter community posts from one known Community timeline. Keep Tweet text, authors, engagement counts, media, and cursor state. Use this X Community Tweets API for Twitter Communities. Save live pages or CSV, JSON, and XLSX exports. This Twitter community scraping guide builds a community timeline export from cursor pages. ## Archive the Unfiltered Community Feed This endpoint accepts a community ID without a keyword. It preserves the returned feed order for timeline archives, content review, and moderation. | Feed decision | Request or response field | Archive rule | | ----------------- | --------------------------------- | ------------------------------------------------- | | Community scope | Path `{id}` | Keep this ID on every exported tweet row. | | Feed coverage | No `q` parameter | Archive posts without text filtering. | | Returned order | Response tweet sequence | Preserve the order instead of re-ranking rows. | | Continuation | `has_next_page` and `next_cursor` | Save each page before advancing. | | Duplicate control | Tweet `id` | Deduplicate moving-feed pages by stable tweet ID. | | Saved export | `community_post_extractor` | Produce CSV, JSON, or XLSX timeline files. | Choose Community Search for keyword filters, `Latest`, or `Top` ranking. ## X Community Tweet Export Questions ### Export Community Tweets Call `GET /api/v1/x/communities/{id}/tweets` for the recent community feed. Keep the Community ID, Tweet ID, text, author, creation time, and media. Store each returned engagement count and cursor checkpoint. Choose Community Search when the workflow requires a keyword. Feed retrieval returns recent posts. Search returns posts that match a query. Save each page and cursor before requesting another page. Deduplicate by stable Tweet ID when new posts enter the feed. Record Community ID, Tweet ID, author ID, text, and creation time. Store each engagement count with media and collection timestamps. Label interrupted, result-capped, or credit-bounded collections as partial. ### Are Twitter Community Posts Public or Private? X says Community posts can appear on the Community page, profiles, global search, and other timelines. Read the official [Communities guide](https://help.x.com/en/using-x/communities) for current visibility and participation rules. A restricted membership policy does not make every visible Community post private. This endpoint returns posts available to the read service for the requested Community. A missing Tweet does not prove deletion, privacy, or moderation. Store the collection time and observed cursor window with every export. ### Where Can I Find Analytics for X Community Posts? Use the returned reply, repost, like, quote, view, and bookmark counts when available. Keep each count beside its Tweet ID and collection time. Compare complete snapshots to measure changes without rewriting the original values. Calculate concrete measures from the captured rows: * Unique Tweet IDs and authors. * Earliest and latest creation times. * Replies, reposts, likes, quotes, views, and bookmarks. * Tweets with photos, videos, or animated GIFs. * Repeated Tweet IDs removed after retries. Use these fields to analyze Community engagement. They do not expose unique viewers, link clicks, conversions, or moderation decisions. Never invent a missing metric or replace it with zero. ### Do I Need to Join a Community to Export Community Tweets? This read route never changes membership. It exports Community posts visible to the connected read context. An export never proves membership. Use X's Communities tab to join a Community or join Communities. ### What Rules Apply to Members Posting in Communities? Each Community sets its posting rules for members. X controls posting access, membership, visibility, and moderation. Community moderators manage roles through X. Xquik reads visible community content without changing those settings. ### Can This API Schedule or Moderate Community Posts? No. This route reads a Community feed. It cannot publish, schedule, hide, report, or remove posts. Use X's own Community tools for role changes and moderation actions. Use the captured community content to prepare a review queue. Require a human decision before any moderation action. Keep private review notes outside Tweet text, author profiles, and exported engagement fields. ### What Does the Twitter Community API Return? The member route returns community profiles. The tweet route returns community posts and author fields. The info route returns community details. Moderator and search routes serve their own focused records. Choose one route per task. Do not combine profile membership and tweet activity into an ambiguous row. Join them later with stable user and community IDs. ### How Do I Build a Complete Community Tweet Export? Save every page and cursor before requesting another page. Deduplicate Tweet IDs when new posts change the live feed. Record the first and last observed Tweet timestamps. Use a saved Community extraction for CSV, JSON, or XLSX exports. Keep direct API pages for live ingestion. Mark interrupted or credit-bounded runs as partial. 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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl https://xquik.com/api/v1/x/communities/1234567890/tweets \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const communityId = "1234567890"; const response = await fetch(`https://xquik.com/api/v1/x/communities/${communityId}/tweets`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const nextCursor = data.has_next_page && data.next_cursor ? data.next_cursor : null; const tweetRows = data.tweets.map((tweet) => ({ community_id: communityId, 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, media_urls: tweet.media?.map((item) => item.mediaUrl).filter(Boolean) ?? [], has_next_page: data.has_next_page, next_cursor: nextCursor, })); process.stdout.write(JSON.stringify(tweetRows, null, 2)); ``` ```python Python theme={null} import json import requests community_id = "1234567890" response = requests.get( f"https://xquik.com/api/v1/x/communities/{community_id}/tweets", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() next_cursor = data.get("next_cursor") if data.get("has_next_page") else None tweet_rows = [ { "community_id": community_id, "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"), "like_count": tweet.get("likeCount"), "reply_count": tweet.get("replyCount"), "retweet_count": tweet.get("retweetCount"), "media_urls": [ item["mediaUrl"] for item in tweet.get("media", []) if item.get("mediaUrl") ], "has_next_page": data.get("has_next_page", False), "next_cursor": next_cursor, } for tweet in data["tweets"] ] print(json.dumps(tweet_rows, indent=2)) ``` ## Direct Community Tweet Handoff Use `GET /x/communities/{id}/tweets` for community monitoring, content review, post exports, or moderation queues. It creates one row per community tweet. Store each Tweet ID in its own record. Preserve the Community ID, author, text, creation time, engagement counts, media, and cursor checkpoint. Store one row per post in the community. Keep author fields and engagement counts with the tweet row for review queues and exports. Keep `has_next_page` beside `next_cursor` for each page. Continue only when `has_next_page` is `true` and `next_cursor` is present. Request 1 to 100 tweets with `pageSize`. The default is 20. Treat the value as an upper bound because the source or available credits can return fewer. Use `community_post_extractor` when the workflow needs a saved job with CSV/JSON/XLSX output. ## Audit a Cursor-Based Community Feed Start each feed run with one numeric community ID. Record the request time and requested `pageSize`. Do not add a keyword to this route. Use community search when the workflow needs text matching. Save every response before advancing its cursor. Record `has_next_page` beside `next_cursor` for each response. Continue only when `has_next_page` is true and `next_cursor` is present. Reuse `next_cursor` without modification. Count returned rows and unique Tweet IDs after each page. A busy community can receive new posts during pagination. Deduplicate repeated Tweet IDs without discarding richer author, engagement, or media fields. Keep the first and last tweet timestamps for every run. They describe the observed feed window. They do not prove a complete historical archive. Label result caps, credit limits, and interrupted cursors explicitly. Store a completion record after the final page. Include community ID, page count, unique Tweet count, first timestamp, and last timestamp. Add collection time and the interrupted cursor when applicable. Store a final `has_next_page` value of `false` for a completed cursor chain. ## Preserve Community Tweet Authors and Media Store each stable Tweet ID in its own record. Keep author ID, username, profile name, and verification when returned. Separate Tweet content from author attributes. Keep replies, reposts, likes, and quotes as measured engagement fields. Store their ISO 8601 collection time. Counts can change after publication. Store each media URL with its owning Tweet ID. Keep media type when available. Do not infer missing images or videos from tweet text. Do not move media URLs into an author profile column. Use JSON when an application needs nested authors or media. Use CSV when analysts need one normalized row. Use XLSX for reviewed spreadsheet handoffs. Document any column flattening beside the exported file. Join later profile snapshots through stable author ID. Usernames and profile names are mutable attributes. Store Tweet creation times separately from later profile checks. ## Compare Community Timeline Snapshots Create a new snapshot ID for every completed feed run. Keep the same community ID across snapshots. Compare stable Tweet IDs before engagement counts. Report newly observed tweets separately from changed tweet fields. A missing Tweet ID may reflect the observed window. It does not prove deletion from X. Compare authors through stable user IDs. Report username or profile changes as attributes. Do not treat them as new authors. Mark every incomplete run before comparison. Exclude partial snapshots from claims about feed growth or activity. Preserve their rows for troubleshooting and cursor recovery. Keep the request time, page count, row count, and completion state. These fields let another reviewer repeat the comparison without guessing its scope. ## Path Parameters Community ID (numeric string). ## Query Parameters Pagination cursor from a previous response. Omit for the first page. Upper bound for tweets per page. Range: 1-100. Default: `20`. ## Which Community Endpoint? Use `GET /x/communities/{id}/tweets` for posts from one known community. Use [`GET /x/communities/tweets`](/api-reference/x/community-search) with `communityId` and `q` to search within one community. Use [`GET /x/communities/{id}/members`](/api-reference/x/community-members) for account rows from the same community. Use [`Create extraction`](/api-reference/extractions/create) with `community_post_extractor`, `community_extractor`, or `community_moderator_explorer` when the workflow needs a saved export. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of community tweets. **Tweet object fields:** Tweet ID. Contains the complete Tweet text. Classifies the Tweet when X returns a type. ISO 8601 creation timestamp. Whether this is a Note Tweet. Omitted if unavailable. Reports the number of likes when available. Reports the number of reposts when available. Reports the number of replies when available. Reports the number of quotes when available. Reports the number of views when available. Reports the number of bookmarks when available. Permalink URL on X. Omitted if unavailable. Reports the Tweet language code when available. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. Identifies the replied-to user when available. Reports the replied-to username when available. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. Returns paid-promotion and AI-generated-media labels when available. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia`. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author X username. Author display name. Reports the author's follower count when available. Whether the author is verified. Omitted if unavailable. Profile picture URL. Omitted if unavailable. Lists media items attached to the Tweet. Omitted when none exist. **Media object fields:** Provides the direct media URL. Lists available video renditions and playback details. Omitted for images. Identifies the attached media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Cursor for the next page. Pass as the `cursor` query parameter. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "Community post content", "createdAt": "2026-02-24T10:00:00.000Z", "likeCount": 150, "retweetCount": 42, "replyCount": 10, "viewCount": 12400, "author": { "id": "987654321", "username": "username", "name": "Xquik", "followers": 12400, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg" } } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid community ID ```json theme={null} { "error": "invalid_community_id" } ``` The community ID is empty or invalid. ### 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. ### 404 Community not found ```json theme={null} { "error": "not_found" } ``` Xquik could not resolve the Community. Check the Community ID. ### 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 } ``` The request exceeded your tier rate limit. Wait for the `Retry-After` header before retrying. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` The read service was unavailable. Retry after a short delay. **Next steps:** [Community Info](/api-reference/x/community-info) for community details, or [Community Members](/api-reference/x/community-members) to list members. # Twitter DM API for Message History & CRM Sync Source: https://docs.xquik.com/api-reference/x/dm-history GET /x/dm/{userId}/history Read participant-scoped Twitter DMs with a connected account. Store private message rows, preserve sender and recipient IDs, and resume pages with next_cursor. ```json theme={null} { "messages": [ { "id": "1234567890123456789", "text": "Hey, how are you?", "senderId": "9876543210", "receiverId": "1234567890" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_user_id" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "dm_not_permitted", "message": "X rejected the DM read. The connected account is not a participant in this conversation, or it needs reauthentication. Reconnect the account on the dashboard and try again." } ``` ```json theme={null} { "error": "account_not_found", "message": "X account not found. Connect it first at /dashboard/account?tab=x-accounts." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* 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)
**1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit This Twitter API DM endpoint reads participant-scoped conversation history. Pass the connected participant account through `account`. Store message IDs and `next_cursor` in private systems. Keep full message text private. Use [Send DM](/api-reference/x-write/send-dm) when the workflow needs a reply. Compare fields with [X's Direct Messages lookup guide](https://docs.x.com/x-api/direct-messages/lookup/introduction). Xquik returns the normalized message fields listed below. Requires a connected X account passed via the `account` query parameter. DM history is participant-scoped, so pass the connected account that belongs to the conversation. DM history responses can contain private message text. Store them in a private support, CRM, warehouse, or agent memory system. Do not write full DM bodies to shared logs or public artifacts. ## Which DM workflow? Use `GET /x/dm/{userId}/history` with `account`, then store `messages[].id` and `next_cursor` in a private system. Use [`POST /x/dm/{userId}`](/api-reference/x-write/send-dm), pass the same connected `account`, and store the returned `messageId`. Use [`POST /x/media`](/api-reference/x-write/upload-media) first, then pass the returned media ID as the only `media_ids` item on the DM send. Use [`GET /x/users/{id}`](/api-reference/x/twitter-profile-lookup) when a workflow starts from a handle and needs the numeric `userId` for history or send calls. ```bash cURL theme={null} curl -G https://xquik.com/api/v1/x/dm/44196397/history \ --data-urlencode "account=your_handle" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq # Page 2 curl -G https://xquik.com/api/v1/x/dm/44196397/history \ --data-urlencode "account=your_handle" \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const userId = "44196397"; const account = "your_handle"; function historyUrl(userId, account, cursor) { const params = new URLSearchParams({ account }); if (cursor) params.set("cursor", cursor); return `https://xquik.com/api/v1/x/dm/${userId}/history?${params}`; } function toDmHistoryRows(page, { account, userId }) { return page.messages.map((message) => ({ record_type: "dm_history", conversation_user_id: userId, sender_account: account, message_id: message.id, sender_id: message.senderId, receiver_id: message.receiverId, message_text: message.text ?? null, created_at: message.createdAt ?? null, media_url: message.mediaUrl ?? null, page_next_cursor: page.has_next_page ? page.next_cursor : null, source_endpoint: `/api/v1/x/dm/${userId}/history`, })); } async function savePrivateDmHistoryRows(rows) { // Replace this with a private CRM, warehouse, or agent memory write. return rows.length; } const response = await fetch(historyUrl(userId, account), { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const data = await response.json(); const historyRows = toDmHistoryRows(data, { account, userId }); await savePrivateDmHistoryRows(historyRows); // Paginate if (data.has_next_page) { const next = await fetch(historyUrl(userId, account, data.next_cursor), { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const nextData = await next.json(); const nextRows = toDmHistoryRows(nextData, { account, userId }); await savePrivateDmHistoryRows(nextRows); } ``` ```python Python theme={null} import requests def to_dm_history_rows(page, account, user_id): return [ { "record_type": "dm_history", "conversation_user_id": user_id, "sender_account": account, "message_id": message["id"], "sender_id": message["senderId"], "receiver_id": message["receiverId"], "message_text": message.get("text"), "created_at": message.get("createdAt"), "media_url": message.get("mediaUrl"), "page_next_cursor": page["next_cursor"] if page["has_next_page"] else None, "source_endpoint": f"/api/v1/x/dm/{user_id}/history", } for message in page["messages"] ] def save_private_dm_history_rows(rows): # Replace this with a private CRM, warehouse, or agent memory write. return len(rows) response = requests.get( "https://xquik.com/api/v1/x/dm/44196397/history", params={"account": "your_handle"}, headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() history_rows = to_dm_history_rows(data, "your_handle", "44196397") save_private_dm_history_rows(history_rows) # Paginate while data["has_next_page"]: data = requests.get( "https://xquik.com/api/v1/x/dm/44196397/history", params={"account": "your_handle", "cursor": data["next_cursor"]}, headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ).json() next_rows = to_dm_history_rows(data, "your_handle", "44196397") save_private_dm_history_rows(next_rows) ``` These examples turn each page into private `dm_history` rows. Store `message_id`, `sender_id`, `receiver_id`, `message_text`, and `created_at`. Also store optional `media_url`, `conversation_user_id`, `sender_account`, and `page_next_cursor` for each page. Keep `message_text` only in private systems. Use IDs, timestamps, media URLs, and job status in shared logs. ## Path parameters Target X user ID for the DM conversation (numeric string). ## Query parameters X handle (without the `@` prefix) of the connected X account used to read the conversation. DM history is participant-scoped, so the account must belong to the conversation. Connect an account on the dashboard before calling this endpoint. Pagination cursor. Use the previous response's `next_cursor` to fetch older messages. Legacy pagination cursor. Use `cursor` for new integrations. When both are present, `cursor` takes precedence. ## Headers Your API key. You can also authenticate with an OAuth bearer token. ## Response ### 200 OK Contains direct messages. **Message object fields:** Identifies the message. Contains message text when available. Identifies the sender. Identifies the recipient. Reports the ISO 8601 timestamp when available. Links attached media when present. Reports whether older messages are available. Provides the next-page cursor. Returns an empty string after the final page. ```json theme={null} { "messages": [ { "id": "1893456789012345678", "text": "Hey, great tool!", "senderId": "44196397", "receiverId": "987654321", "createdAt": "2026-02-24T10:00:00.000Z" } ], "has_next_page": true, "next_cursor": "1893456789012345677" } ``` ### 400 Invalid user ID ```json theme={null} { "error": "invalid_user_id" } ``` The user ID is empty or invalid. ### 400 Account required ```json theme={null} { "error": "account_required", "message": "Provide ?account= for a connected X account. DM history requires a connected participant account." } ``` The `account` query parameter was missing or empty. Pass the handle of a connected X account that participates in the conversation. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Supply a valid API key or OAuth bearer token. ### 402 Insufficient credits ```json theme={null} { "error": "insufficient_credits" } ``` Metered access requires enough available credits. Possible error values include `no_subscription`, `subscription_inactive`, `no_credits`, and `insufficient_credits`. ### 403 DM not permitted ```json theme={null} { "error": "dm_not_permitted", "message": "X rejected the DM read. The connected account is not a participant in this conversation, or it needs reauthentication. Reconnect the account on the dashboard and try again." } ``` X rejected the DM read. Use a participating connected account. If it needs reauthentication, reconnect it before retrying. ### 403 Account restricted ```json theme={null} { "error": "account_restricted" } ``` The connected X account is suspended, locked, or otherwise restricted. Use a different connected account. ### 403 Account needs reauth ```json theme={null} { "error": "account_needs_reauth" } ``` Reconnect the connected account from the dashboard. ### 404 Account not found ```json theme={null} { "error": "account_not_found" } ``` The requested connected X account was not found. Connect it first or pass another account handle. ### 429 Rate Limit Exceeded ```json theme={null} { "error": "rate_limit_exceeded", "retryAfter": 60 } ``` You exceeded your tier's rate limit. Wait for `Retry-After` before retrying. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` The opt-in normalized contract returns 424 when the read service fails. Send `xquik-api-contract: 2026-04-29` to opt in. Default v1 returns 502. ### 502 X API unavailable ```json theme={null} { "error": "x_api_unavailable" } ``` The read service failed. Retry after a short delay. ## Twitter DM API Questions ### Can I Retrieve DM History? Yes. Call `GET /x/dm/{userId}/history` with a participating connected account. ### Can I Send a Reply Through This Endpoint? No. Use [Send DM](/api-reference/x-write/send-dm). This endpoint reads history only. ### Does This Endpoint Create Direct Message Webhooks? No. It reads participant-scoped history when called. It does not register webhooks. ### How Do I Authenticate? Send an API key or OAuth bearer token. ### How Do I Handle a Rate Limit? Wait for `Retry-After`, then retry the same page. ## History sync handoff Use this endpoint when a CRM, support desk, warehouse job, or agent needs participant-scoped DM context before sending a reply. Store `messages[].id` as the external DM ID for CRM notes, support tickets, warehouse rows, or agent memory. Store `messages[].senderId` and `messages[].receiverId` with the connected `account` so each private conversation stays tied to the correct sender. Store `next_cursor` when `has_next_page` is true, then pass it as `cursor` on the next sync job. Store optional `messages[].mediaUrl` with `messages[].createdAt` when a DM includes an image, GIF, or video attachment. **Related:** [Direct Message Workflow](/guides/direct-message-workflow) for lookup, participant-scoped history sync, `messageId` storage, and media handoff; [Get User](/api-reference/x/twitter-profile-lookup) to resolve the recipient `userId`; [Send DM](/api-reference/x-write/send-dm) to reply from the connected account; [Upload Media](/api-reference/x-write/upload-media) when a reply needs one uploaded `mediaId`. # Twitter Media Downloader API for Photos & Video Source: https://docs.xquik.com/api-reference/x/download-media POST /x/media/download Download images, videos, and animated GIFs from 1-50 tweets. Return a gallery URL and download metadata for bulk review. Includes API request examples. ```json theme={null} { "tweetId": "1234567890", "galleryUrl": "https://xquik.com/gallery/abc123", "cacheHit": false } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "tweet_not_found", "message": "Tweet not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
* 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)
Use this Twitter media downloader API with 1-50 tweet IDs or URLs. Download Twitter media from one tweet or a batch of 50. Each response contains a gallery of photos, videos, and GIFs. **1 credit per fresh tweet processed with media** · cache hits are free · [All plans](https://xquik.com/#pricing) from \$0.00012/credit This endpoint creates a saved media gallery from 1-50 tweet URLs or IDs. The response gives a `galleryUrl` plus cache or bulk counts. It does not return per-file downloads, file metadata, or an uploaded `mediaId`. ```bash cURL (single) theme={null} curl -X POST https://xquik.com/api/v1/x/media/download \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "tweetInput": "1893456789012345678" }' | jq ``` ```bash cURL (bulk) theme={null} curl -X POST https://xquik.com/api/v1/x/media/download \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "tweetIds": ["1893456789012345678", "1893456789012345999", "1893456789012346000"] }' | jq ``` ```javascript Node.js theme={null} const singleTweetId = "1893456789012345678"; // Single tweet const single = await fetch("https://xquik.com/api/v1/x/media/download", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ tweetInput: singleTweetId }), }); const singleResult = await single.json(); const singleRow = { input_mode: "single", requested_tweet_id: singleTweetId, tweet_id: singleResult.tweetId, gallery_url: singleResult.galleryUrl, cache_hit: singleResult.cacheHit, }; process.stdout.write(`${JSON.stringify(singleRow)}\n`); const bulkTweetIds = ["1893456789012345678", "1893456789012345999"]; // Bulk (up to 50 tweets) const bulk = await fetch("https://xquik.com/api/v1/x/media/download", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ tweetIds: bulkTweetIds }), }); const bulkResult = await bulk.json(); const bulkRow = { input_mode: "bulk", requested_tweet_ids: bulkTweetIds, gallery_url: bulkResult.galleryUrl, successful_tweet_count: bulkResult.totalTweets, media_item_count: bulkResult.totalMedia, }; process.stdout.write(`${JSON.stringify(bulkRow)}\n`); ``` ```python Python theme={null} import json import requests single_tweet_id = "1893456789012345678" # Single tweet single = requests.post( "https://xquik.com/api/v1/x/media/download", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"tweetInput": single_tweet_id}, ) single_result = single.json() single_row = { "input_mode": "single", "requested_tweet_id": single_tweet_id, "tweet_id": single_result["tweetId"], "gallery_url": single_result["galleryUrl"], "cache_hit": single_result["cacheHit"], } print(json.dumps(single_row)) bulk_tweet_ids = ["1893456789012345678", "1893456789012345999"] # Bulk (up to 50 tweets) bulk = requests.post( "https://xquik.com/api/v1/x/media/download", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={"tweetIds": bulk_tweet_ids}, ) bulk_result = bulk.json() bulk_row = { "input_mode": "bulk", "requested_tweet_ids": bulk_tweet_ids, "gallery_url": bulk_result["galleryUrl"], "successful_tweet_count": bulk_result["totalTweets"], "media_item_count": bulk_result["totalMedia"], } print(json.dumps(bulk_row)) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "log" "net/http" ) type MediaDownloadResponse struct { CacheHit bool `json:"cacheHit"` GalleryURL string `json:"galleryUrl"` TweetID string `json:"tweetId"` TotalMedia int `json:"totalMedia"` TotalTweets int `json:"totalTweets"` } type MediaDownloadRow struct { InputMode string `json:"input_mode"` RequestedTweetID string `json:"requested_tweet_id"` TweetID string `json:"tweet_id"` GalleryURL string `json:"gallery_url"` CacheHit bool `json:"cache_hit"` } func main() { // Single tweet tweetID := "1893456789012345678" payload, _ := json.Marshal(map[string]interface{}{ "tweetInput": tweetID, }) req, err := http.NewRequest("POST", "https://xquik.com/api/v1/x/media/download", bytes.NewReader(payload)) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() var result MediaDownloadResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { log.Fatal(err) } row := MediaDownloadRow{ InputMode: "single", RequestedTweetID: tweetID, TweetID: result.TweetID, GalleryURL: result.GalleryURL, CacheHit: result.CacheHit, } output, err := json.Marshal(row) if err != nil { log.Fatal(err) } fmt.Println(string(output)) } ``` ## Headers Your API key. You can also authenticate with an OAuth bearer token. Must be `application/json`. ## Body Use `tweetIds` for bulk downloads. If `tweetIds` is present with at least 1 string value, the route uses bulk mode and ignores single-tweet fields. Otherwise, use `tweetInput`, `tweetId`, or `tweetUrl` for a single tweet. Tweet URL or numeric tweet ID for a single download. Accepts `x.com` and `twitter.com` URL formats. Numeric tweet ID alias for `tweetInput`. Use it when `tweetInput` is absent. Tweet URL alias for `tweetInput`. Use it when both earlier fields are absent. Array of tweet URLs or IDs for bulk download. Maximum 50 string items. The route skips invalid IDs. It fails only when no valid tweet IDs remain. ## Media download handoff Use this endpoint when your agent needs a saved gallery for tweet images, videos, or GIFs. Write one manifest row per request so downstream jobs can store the gallery link without treating it as an uploaded media ID or an individual media file URL. Store `gallery_url` from `galleryUrl` as the durable link for downloaded media. Store `requested_tweet_id`, `tweet_id`, and `cache_hit`. `cacheHit: true` means the single-tweet request used cached media and is free. Store `requested_tweet_ids`, `successful_tweet_count` from `totalTweets`, and `media_item_count` from `totalMedia`. `totalTweets` counts successful tweets with media after invalid or failed IDs are skipped. Send `tweetIds` for bulk. When it contains at least 1 string, bulk mode ignores `tweetInput`, `tweetId`, and `tweetUrl`. Keep `tweetIds` at 50 items or fewer. Split larger backfills into multiple requests. This endpoint creates a gallery download, not an uploaded media ID. Use [Upload Media](/api-reference/x-write/upload-media) before DMs or hosted tweet assets. ## Store a tweet media download manifest Keep one manifest row per single or bulk request. A gallery URL represents the download result. A gallery URL is neither an uploaded media ID nor a direct asset URL. | Manifest column | Response or request source | Reconciliation rule | | ------------------------ | -------------------------------- | ------------------------------------------------------------ | | `input_mode` | Presence of non-empty `tweetIds` | Store `single` or `bulk`. | | `requested_tweet_ids` | Normalized request inputs | Preserve every valid ID submitted to the download route. | | `tweet_id` | Single response `tweetId` | Store the resolved Tweet ID for single mode. | | `gallery_url` | Response `galleryUrl` | Use as the shareable downloaded-media result. | | `cache_hit` | Single response `cacheHit` | Record whether the single download used free cached media. | | `successful_tweet_count` | Bulk response `totalTweets` | Count only successful tweets that contained media. | | `media_item_count` | Bulk response `totalMedia` | Reconcile the number of downloaded images, videos, and GIFs. | | `requested_at` | Integration timestamp | Audit when the download request started. | | `completed_at` | Integration timestamp | Record when your integration stores the gallery. | Use these outcomes when reconciling a download request: * `cacheHit: true` confirms a free cached single result. * `cacheHit: false` confirms a fresh single result. * If `totalTweets` is below the valid input count, review skipped Tweet IDs. * A `400 no_media` response means the source tweet has no downloadable media. * Preserve rejected inputs while processing valid Tweet IDs. * Split more than 50 inputs into groups of 50 or fewer. Fresh downloads cost 1 credit per tweet processed with media. Cached single downloads return `cacheHit: true` and are free. Bulk responses do not return `freshCount`; store the request IDs with `totalTweets` and `totalMedia` for reconciliation. ## Response ### 200 OK (single) Identifies the resolved tweet. Links the shareable gallery containing all downloaded media. Shows whether the cache supplied the media. Cached requests consume no credits. ```json theme={null} { "tweetId": "1893456789012345678", "galleryUrl": "https://xquik.com/gallery/abc123", "cacheHit": false } ``` ### 200 OK (bulk) Links the combined gallery containing media from all tweets. Counts processed tweets with media. Counts downloaded images, videos, and GIFs. ```json theme={null} { "galleryUrl": "https://xquik.com/gallery/def456", "totalTweets": 3, "totalMedia": 7 } ``` ### 400 Invalid Input ```json theme={null} { "error": "invalid_input", "message": "Invalid request body" } ``` Missing, malformed, or non-JSON request body. Malformed JSON can also return `invalid_json`. ### 400 Invalid Tweet ID ```json theme={null} { "error": "invalid_tweet_id", "message": "Tweet ID is empty or invalid" } ``` The provided tweet ID or URL could not be resolved to a valid tweet ID. ### 400 Too Many Tweets ```json theme={null} { "error": "too_many_tweets", "message": "Max 50 tweets per request" } ``` The `tweetIds` array exceeds the 50-item limit. Split into multiple requests. ### 400 No Media ```json theme={null} { "error": "no_media", "message": "Tweet has no downloadable media" } ``` The tweet does not contain any images, videos, or GIFs. ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated", "message": "Missing or invalid API key" } ``` Supply a valid API key or OAuth bearer token. ### 402 Insufficient Credits ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` The available balance cannot cover the download. Top up or subscribe from the [dashboard billing page](https://dashboard.xquik.com/en/account?tab=subscription). ### 404 Tweet Not Found ```json theme={null} { "error": "tweet_not_found", "message": "Tweet not found" } ``` The tweet ID is valid, but the tweet cannot be fetched or no longer exists. ### 429 Rate Limit Exceeded ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` The API key, user, or plan tier is sending requests too quickly. Respect the `Retry-After` header before retrying. ### 502 X API unavailable ```json theme={null} { "error": "x_api_unavailable" } ``` The read service failed. Retry after a short delay. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` The opt-in normalized contract returns 424 when the read service fails. Send `xquik-api-contract: 2026-04-29` to opt in. Default v1 returns 502. ## Twitter Media Downloader Questions ### How Do I Download Twitter Media? Copy the tweet link or numeric ID. Send it through `tweetInput`. The gallery can contain Twitter videos and GIFs plus images. ### Can I Save Twitter Videos or GIFs on a Phone? Yes. Open the returned `galleryUrl` in a web browser. The API does not return direct per-file URLs. ### Does This Work Like a Browser Extension? No. This is a server-side API. Keep the API key or bearer token server-side. ### Can I Download Twitter Video Files Directly? No. This Twitter video downloader route creates a gallery. It does not expose per-file downloads or resolution controls. ### Can I Download Media From Many Tweets? Yes. Send 1-50 tweet IDs through `tweetIds`. The response returns one gallery with tweet and media counts. First download is metered and counts toward your monthly credit allowance. Subsequent requests for the same tweet return cached URLs at no cost (`cacheHit: true`). All downloads are saved to your gallery at `https://xquik.com/gallery`. This endpoint accepts an API key or OAuth bearer token. Keep either credential server-side. **Related:** [Get Tweet](/api-reference/x/get-tweet) to look up tweet details and metrics, or use the `xquik` [MCP tool](/mcp/tools#xquik) for AI agent access. # How to See Who Liked My Tweet with Twitter API Source: https://docs.xquik.com/api-reference/x/favoriters GET /x/tweets/{id}/favoriters See who liked a tweet with user profiles, follower counts, verification fields, cursor checkpoints, visibility limits & exact Twitter API examples for exports. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "favoriters_unavailable", "message": "Users who liked this post are unavailable. Use retweeters or replies instead." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) Use this Twitter API to see who liked one public tweet. The response returns profiles only when X exposes liker identities. The canonical route is `GET /api/v1/x/tweets/{id}/favoriters`. X does not expose liker identities for every post. When a post reports likes but no liker identities are available, the endpoint returns `424 favoriters_unavailable`. Do not interpret this error as zero likes or proof that a specific user did not participate. ```bash First page theme={null} curl https://xquik.com/api/v1/x/tweets/1893456789012345678/favoriters \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```bash Next page theme={null} curl -G https://xquik.com/api/v1/x/tweets/1893456789012345678/favoriters \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const tweetId = "1893456789012345678"; const response = await fetch(`https://xquik.com/api/v1/x/tweets/${tweetId}/favoriters`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); if (!response.ok) throw new Error(JSON.stringify(data)); const nextCursor = data.has_next_page ? data.next_cursor : null; const likerRows = data.users.map((user) => ({ source_tweet_id: tweetId, liker_id: user.id, username: user.username, display_name: user.name, follower_count: user.followers ?? null, following_count: user.following ?? null, verified: user.verified ?? false, verified_type: user.verifiedType ?? null, profile_image_url: user.profilePicture ?? null, })); const checkpoint = { source_tweet_id: tweetId, next_cursor: nextCursor }; for (const row of likerRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } process.stdout.write(`${JSON.stringify({ checkpoint })}\n`); ``` ```python Python theme={null} import json import requests tweet_id = "1893456789012345678" response = requests.get( f"https://xquik.com/api/v1/x/tweets/{tweet_id}/favoriters", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() response.raise_for_status() next_cursor = data["next_cursor"] if data["has_next_page"] else None liker_rows = [ { "source_tweet_id": tweet_id, "liker_id": user["id"], "username": user["username"], "display_name": user["name"], "follower_count": user.get("followers"), "following_count": user.get("following"), "verified": user.get("verified", False), "verified_type": user.get("verifiedType"), "profile_image_url": user.get("profilePicture"), } for user in data["users"] ] checkpoint = {"source_tweet_id": tweet_id, "next_cursor": next_cursor} for row in liker_rows: print(json.dumps(row)) print(json.dumps({"checkpoint": checkpoint})) ``` The Node.js and Python snippets write JSON Lines liker rows plus a separate checkpoint instead of raw response pages. Persist each mapped row and the latest `next_cursor` so an import, giveaway verifier, CRM sync, or agent job can resume from the last completed page without duplicate rows. ## Direct tweet liker handoff Use `GET /api/v1/x/tweets/{id}/favoriters` when a workflow needs one row per visible account that liked a post. Use these rows for giveaway checks, CRM imports, audience reviews, or follow-up jobs. Store `source_tweet_id`, `liker_id`, `username`, `display_name`, `follower_count`, `following_count`, `verified`, `verified_type`, `profile_image_url`, and `next_cursor`. Store `users[]` as the visible profile rows for accounts that liked the source post. Store `users[].id` as `liker_id` with `source_tweet_id` for idempotent imports and giveaway checks. Store `users[].username` and `users[].name` for handles, labels, and review queues. Store `description`, `location`, `url`, and `profilePicture` when returned for CRM and warehouse enrichment. Store `followers`, `following`, `verified`, and `verifiedType` for scoring, filters, and outreach priority. Use DM endpoints only after a user-approved message flow. Treat the write response as the delivery authority. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. Use `users.length`, not a requested page size, for row counts. Low balances can return fewer rows. Direct tweet liker reads cost 1 credit per user returned. Low credit balances can return fewer users than a full page; zero affordable results return `402 insufficient_credits`. ## Tweet Liker Questions ### How Do I See Who Liked My Tweet? Pass the tweet's numeric ID. The response lists visible user profiles. Continue pagination only when the response confirms another page. [X documents this read intent as Get Liking Users.](https://docs.x.com/x-api/posts/get-liking-users) ### Why Can't I See Who Liked My Tweet? X does not expose every liker set. A tweet can show a like count while its liker profiles remain hidden. `424 favoriters_unavailable` does not mean zero likes. Deleted, protected, blocked, or withheld accounts may not appear. ### Does This Show an Account's Private Likes? No. This endpoint returns visible users for one source tweet. It does not return an account's Likes tab or private like history. ### How Do I Export Tweet Likers? The Node.js and Python examples write one JSON Lines row per visible user. Store the source tweet ID beside each profile. Use `next_cursor` to resume an export. Use `toolType=favoriters` for a saved CSV, JSON, or XLSX job. ### How Can I Track and Analyze Tweet Likes? Run timestamped snapshots and compare them by numeric user ID. This read endpoint does not send notifications for future likes. A like does not prove endorsement, purchase intent, or affiliation. Report only returned fields. ## Path parameters Tweet ID (numeric string). ## Query parameters Pagination cursor from `next_cursor` in a previous response. Omit it on the initial request. Pass it only when `has_next_page` is true. Profiles per page. Range: `20-200`. Defaults to `200`. ## Which tweet engagement endpoint? Use `GET /x/tweets/{id}/favoriters` for user profiles that liked one source tweet. Use [`GET /x/tweets/{id}/retweeters`](/api-reference/x/retweeters) for user profiles that reposted one source tweet. Use [`GET /x/tweets/{id}/quotes`](/api-reference/x/tweet-quotes) when you need tweet rows that quote the source tweet. Use [`GET /x/tweets/{id}/replies`](/api-reference/x/tweet-replies) when you need reply tweet rows under the source tweet. Use [`Create extraction`](/api-reference/extractions/create) with `toolType=favoriters` when you need a saved job or CSV, JSON, or XLSX export. Use [`Send DM`](/api-reference/x-write/send-dm) only after your workflow has a user-approved outreach step. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of visible users who liked the post. **User object fields:** X user ID. X username. Display name. Profile bio. This value records the follower count. Following count. Verified status. Profile image URL. Profile location. Account creation date (ISO 8601). This value records the total tweet count. X may omit it. This value contains the cover image URL. X may omit it. This value records the media tweet count. X may omit it. Website URL from profile. Omitted if empty. This value records the liked tweet count. X may omit it. X sets this flag for accounts with custom timelines. X may omit it. X sets this flag for translator accounts. X may omit it. Country codes where the account is withheld. Omitted if empty. X sets this flag for sensitive accounts. X may omit it. This array contains pinned tweet IDs. Omitted if none. X sets this flag for automated accounts. X may omit it. Username of the account operator if automated. Omitted if not automated. X sets this flag when it cannot return the account. X may give a reason for a missing account. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Use this object for bio text and linked profile entities. X may omit it. X sets this flag for X Premium verification. X may omit it. Use this field for normalized verification status. X may omit it. This value contains the profile banner URL. X may omit it. X sets this flag for protected accounts. X may omit it. Role within the requested community context. Omitted outside community results. Whether more results are available. Opaque cursor for the next page. Empty string when no more results. ### 401 Unauthenticated Anonymous requests receive `WWW-Authenticate: Bearer`. This is not a Payment challenge. ### 402 Payment required Account users can subscribe or add credits. A guest wallet can open checkout. Confirm payment before continuing. **Related:** [Retweeters](/api-reference/x/retweeters) · [Quote tweets](/api-reference/x/tweet-quotes) · [Tweet replies](/api-reference/x/tweet-replies) # Twitter Followers API, Profile Export & Cursors Source: https://docs.xquik.com/api-reference/x/followers GET /x/users/{id}/followers Get an X account's followers by username or user ID with cursor pagination for CRM, warehouse, audience, and agent workflows. Includes request fields. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "Maximum coverage is busy. Retry shortly." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
**1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) Get followers returns follower profiles for one X account by username or numeric user ID. It is also useful as a Follower Export API, X followers API, or Twitter followers API. The canonical endpoint remains `GET /api/v1/x/users/{id}/followers`. 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. 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`. 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. Finished, expired, superseded, or identity-mismatched cursors return `410 coverage_cursor_gone`. The response omits `Retry-After`. Restart without a cursor. Deduplicate restarted results by `id`. Malformed cursors return `400 invalid_coverage_cursor`. Restart without them. ```bash cURL theme={null} # Username follower page curl "https://xquik.com/api/v1/x/users/username/followers?pageSize=200" \ -H "x-api-key: xq_your_api_key_here" | jq # Numeric user ID follower page curl "https://xquik.com/api/v1/x/users/44196397/followers?pageSize=200" \ -H "x-api-key: xq_your_api_key_here" | jq # Resume with next_cursor from the previous page curl -G "https://xquik.com/api/v1/x/users/username/followers" \ --data-urlencode "cursor=DAACCgACGE..." \ --data-urlencode "pageSize=200" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const userIdOrUsername = "username"; let pageCursor = ""; const seenCursors = new Set(); for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { const params = new URLSearchParams({ pageSize: "200" }); if (pageCursor !== "") params.set("cursor", pageCursor); const response = await fetch( `https://xquik.com/api/v1/x/users/${userIdOrUsername}/followers?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" } }, ); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const importRows = page.users.map((user) => ({ source_user_id_or_username: userIdOrUsername, x_user_id: user.id, x_username: user.username, display_name: user.name, description: user.description ?? null, location: user.location ?? null, website_url: user.url ?? null, follower_count: user.followers ?? null, following_count: user.following ?? null, verified: user.verified ?? false, verified_type: user.verifiedType ?? null, profile_picture_url: user.profilePicture ?? null, cover_picture_url: user.coverPicture ?? null, account_created_at: user.createdAt ?? null, statuses_count: user.statusesCount ?? null, page_index: pageIndex, page_cursor: pageCursor, next_cursor: page.next_cursor, has_next_page: page.has_next_page, })); for (const row of importRows) 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; } ``` ```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 = {"pageSize": "200"} if page_cursor: params["cursor"] = page_cursor response = requests.get( f"https://xquik.com/api/v1/x/users/{user_id_or_username}/followers", params=params, headers={"x-api-key": "xq_your_api_key_here"}, ) page = response.json() if not response.ok: raise RuntimeError(page) for user in page["users"]: import_row = { "source_user_id_or_username": user_id_or_username, "x_user_id": user["id"], "x_username": user["username"], "display_name": user["name"], "description": user.get("description"), "location": user.get("location"), "website_url": user.get("url"), "follower_count": user.get("followers"), "following_count": user.get("following"), "verified": user.get("verified", False), "verified_type": user.get("verifiedType"), "profile_picture_url": user.get("profilePicture"), "cover_picture_url": user.get("coverPicture"), "account_created_at": user.get("createdAt"), "statuses_count": user.get("statusesCount"), "page_index": page_index, "page_cursor": page_cursor, "next_cursor": page["next_cursor"], "has_next_page": page["has_next_page"], } print(json.dumps(import_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"] ``` The Node.js and Python snippets write JSON Lines import rows instead of raw follower pages. Persist each mapped row and the latest `next_cursor` in your sync job so it can resume from the last completed page. ## Direct follower handoff Use `GET /api/v1/x/users/{id}/followers` when a CRM, warehouse, audience, or agent workflow needs follower rows for one profile now. The examples above write JSON Lines rows with `source_user_id_or_username`, `x_user_id`, `x_username`, `display_name`, profile enrichment, segmentation fields, `page_index`, `page_cursor`, `next_cursor`, and `has_next_page` for imports or upserts. Use [`follower_explorer`](/guides/follower-export-crm) when you need an estimated job, saved extraction, or CSV/JSON/XLSX file export. ## Choose live API or saved export Use this endpoint for current JSON pages when an app, queue, or agent can store `next_cursor` and process `users[]` immediately. Use `follower_explorer` when the job needs a cost estimate, reusable extraction ID, stored result pages, or CSV/JSON/XLSX files after completion. Call `GET /x/users/{id}/followers` with `pageSize` and `cursor` for low-latency imports, enrichment, queues, or agent handoffs. Run `follower_explorer` when operators need estimates, job status, paginated saved rows, or file downloads. Store `users[]` as the follower profile rows returned on this page. Store `users[].id` as `x_user_id` for CRM, warehouse, audience, and agent dedupe. Store `users[].username` and `users[].name` for handles, labels, enrichment, and dedupe. Store `users[].description`, `location`, and `url` when returned. Empty profile fields are omitted. Store `users[].followers`, `users[].following`, `verified`, and `verifiedType` for filters and scoring. Store `users[].profilePicture` and `coverPicture` for enrichment, review queues, or profile previews. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. Use `users.length`, not the requested `pageSize`, for row counts and budget checks. Low balances can return fewer rows. Automatic `pageSize` accepts 20 to 300. Standard mode accepts 20 to 200. Paid calls can return fewer rows. Treat `users.length` as the billable row count. Zero affordable results return `402 insufficient_credits`. For high-volume follower pulls, de-duplicate profiles by `id`, continue through empty pages when the cursor advances, and stop with a partial-result status when `next_cursor` is missing or repeats. ## Build a Twitter follower tracker Store complete follower snapshots with the source profile and collection time. Compare snapshots by numeric user ID. Classify IDs that appear only in the latest snapshot as newly observed followers. Classify IDs missing from the latest complete snapshot as removed followers. Do not compare partial exports. Keep username, profile name, verification state, follower counts, and profile image beside each ID. These fields make review easier. Use the numeric ID for the actual comparison because usernames can change. Save every cursor page before marking a snapshot complete. Record the final row count and completion time. A failed or credit-limited run should remain partial. | Follower tracker column | Source | Comparison rule | | ----------------------- | ------------------------------------- | ------------------------------------------------------------- | | `source_x_user_id` | Resolve the requested account once | Keep the source account stable across snapshots. | | `snapshot_id` | Generate for each complete collection | Compare rows only inside the intended snapshot pair. | | `collected_at` | Set when each cursor page is stored | Preserve collection order and snapshot recency. | | `x_user_id` | `users[].id` | Use as the stable follower identity. | | `x_username` | `users[].username` | Display the current handle, but never use it as the join key. | | `follower_count` | `users[].followers` | Preserve the observed audience size for segmentation. | | `verified` | `users[].verified` | Preserve the observed verification state for filtering. | | `profile_picture_url` | `users[].profilePicture` | Use in review queues and CRM profile previews. | | `snapshot_complete` | Set after the final cursor page | Compare removals only when both snapshots are complete. | | `row_count` | Sum the stored `users.length` values | Reconcile stored followers with the completed export. | | Snapshot result | ID condition | Tracker action | | ----------------------- | --------------------------------------------- | ----------------------------------------------------------------- | | Newly observed follower | Present only in the latest complete snapshot | Add the first-seen time and latest follower profile fields. | | Continuing follower | Present in both complete snapshots | Update mutable username, profile, count, and verification fields. | | Removed follower | Present only in the earlier complete snapshot | Add the last-seen time; do not delete the historical row. | | Unknown change | Either snapshot is partial | Preserve both snapshots and defer follower-change classification. | ## Which follower endpoint? * Use `GET /api/v1/x/users/{id}/followers` for one account's live follower page. * Use [`follower_explorer`](/guides/follower-export-crm) when you need a saved job, CSV, JSON, or XLSX export. * Use `GET /api/v1/x/users/{id}/following` for accounts the user follows. * Use `GET /api/v1/x/users/{id}/verified-followers` when you only need verified followers. ## Path parameters Username or numeric user ID. For example, use `username` or `44196397`. ## Query parameters Pass `next_cursor` back unchanged. New Xquik cursors resume automatic coverage. Existing unprefixed cursors keep legacy behavior. 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. Legacy cursor alias for `cursor`. When both are present, `cursor` wins. Automatic pages accept `20` through `300`. Standard pages accept `20` through `200`. The default is `200`. Credits can reduce the returned row count. With `mode=coverage`, set a one-shot cap from `1` through `10000`. Otherwise, this is a legacy page size alias. `pageSize` wins. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of follower profiles. **User object fields:** X user ID. X username. Display name. Profile bio. Omitted if empty. Follower count. Following count. Whether the user is verified. Profile picture URL. Profile location. Omitted if empty. ISO 8601 account creation timestamp. Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines. Omitted if unavailable. Whether the user is an X translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. IDs of pinned tweets. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable. Omitted if available. Reason the account is unavailable. Omitted if available. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. Role within the requested community context. Omitted outside community results. Whether more results are available. Cursor for the next page. ```json theme={null} { "users": [ { "id": "987654321", "username": "username", "name": "Xquik", "followers": 10000, "following": 500, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/xquik/photo.jpg", "description": "All-in-one X automation platform" } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid user ID ```json theme={null} { "error": "invalid_user_id", "message": "User not found or invalid user ID. Check the username or ID." } ``` ### 404 User not found ```json theme={null} { "error": "user_not_found", "message": "X user not found. Check the username." } ``` ### 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 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Follower Export CRM Workflow](/guides/follower-export-crm) for saved CSV, JSON, or XLSX files for imports or upserts, [Following](/api-reference/x/following), [Verified Followers](/api-reference/x/verified-followers), and [Followers You Know](/api-reference/x/followers-you-know). # Twitter Mutual Followers API & Shared Connections Source: https://docs.xquik.com/api-reference/x/followers-you-know GET /x/users/{id}/followers-you-know Retrieve mutual X followers between the authenticated context and one target user for warm-intro, CRM, scoring, and agent workflows. See request fields. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
## Choose Mutual Followers Use this route for mutual followers in the authenticated context. It supports warm introductions, CRM scoring, and pre-message checks. Use profile lookup when mutual relationship rows are unnecessary. 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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) Get followers you know returns mutual followers between the authenticated context and one target X user. It is also useful as a mutual followers API, followers you know API, X mutual followers API, or Twitter mutual followers API. The canonical endpoint remains `GET /api/v1/x/users/{id}/followers-you-know`. ```bash First page theme={null} curl https://xquik.com/api/v1/x/users/44196397/followers-you-know \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```bash Next page theme={null} curl -G https://xquik.com/api/v1/x/users/44196397/followers-you-know \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const userId = "44196397"; const response = await fetch(`https://xquik.com/api/v1/x/users/${userId}/followers-you-know`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const mutualRows = data.users.map((user) => ({ target_user_id: userId, x_user_id: user.id, username: user.username, display_name: user.name, follower_count: user.followers ?? null, verified: user.verified ?? false, verified_type: user.verifiedType ?? null, profile_image_url: user.profilePicture ?? null, })); const nextCursor = data.has_next_page ? data.next_cursor : null; const checkpoint = { target_user_id: userId, next_cursor: nextCursor }; for (const row of mutualRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } process.stdout.write(`${JSON.stringify(checkpoint)}\n`); ``` ```python Python theme={null} import json import requests user_id = "44196397" response = requests.get( f"https://xquik.com/api/v1/x/users/{user_id}/followers-you-know", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() mutual_rows = [ { "target_user_id": user_id, "x_user_id": user["id"], "username": user["username"], "display_name": user["name"], "follower_count": user.get("followers"), "verified": user.get("verified", False), "verified_type": user.get("verifiedType"), "profile_image_url": user.get("profilePicture"), } for user in data["users"] ] next_cursor = data["next_cursor"] if data["has_next_page"] else None checkpoint = {"target_user_id": user_id, "next_cursor": next_cursor} for row in mutual_rows: print(json.dumps(row)) print(json.dumps(checkpoint)) ``` The Node.js and Python snippets shape durable mutual follower rows instead of printing the full response page. Persist the rows with the checkpoint so a worker can resume pagination with `next_cursor` without duplicating already imported profiles. ## Direct mutual followers handoff Use `GET /x/users/{id}/followers-you-know` when a sales, community, recruiting, support, CRM, or agent workflow needs one JSON page of mutual followers for a target user. The path `id` is the target numeric X user ID. The endpoint returns people who follow both the authenticated context and the target user. Store `users[]` as the mutual follower profile rows returned on this page. Store `users[].id` as `x_user_id` for CRM, warehouse, scoring, and agent dedupe. Store `users[].username` and `users[].name` for handles, owner review, routing, and handoff labels. Store `users[].description`, `location`, `url`, `profilePicture`, and `coverPicture` when returned. Store `users[].followers`, `users[].following`, `verified`, and `verifiedType` for scoring and queue priority. Use DM endpoints only after a user-approved message flow. Treat the write response as the delivery authority. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. Direct mutual followers cost 1 credit per user returned. Low credit balances can return fewer users than a full page; zero affordable results return `402 insufficient_credits`. ## Explain mutual follower context Use this route to find profiles that connect the acting account with another profile. Keep both account identities with every returned user. Mutual-follower rows can support: * Relationship context during account review. * Introductions through already known profiles. * Trust research around a public conversation. * CRM notes tied to shared followers. Store user ID, username, profile name, verification state, and follower counts. Also store the target profile ID and acting account. Do not describe a mutual follower as an endorsement. The route reports a public relationship, not intent. Avoid inferring consent, employment, or affiliation. Paginate by cursor and deduplicate by user ID. Keep a collection timestamp because follow relationships can change. ## Build a Mutual Connection Brief Keep the target user ID with every mutual follower result. Store each user ID, username, profile summary, and biography. Add verification state, follower count, request time, and cursor. Rank mutual followers for the downstream review. The API does not declare endorsement, relationship strength, or personal familiarity. Refresh the target profile before a time-sensitive decision. Use stable user IDs when usernames change. Do not merge these results with verified followers without a source column. Mutual context and verification answer different questions. ## Prepare a Warm-Introduction Review Begin with the acting account and target account. Store both stable user IDs. Every returned profile belongs to the mutual-follower context between them. Keep each mutual follower's stable user ID, username, and profile name. Add biography, location, verification, and audience counts. Add the page cursor and collection time. These fields let reviewers identify the shared connection. Do not rank profiles from follower count alone. Review biography, location, recent profile context, and the downstream purpose. Record the chosen priority outside returned API fields. Never describe a mutual follower as an introduction, endorsement, or consent. The route reports a public relationship. A person must approve any outreach or introduction step. When a reviewer approves outreach, preserve the mutual-follower snapshot. Link the later message result through stable user IDs. Keep message content and delivery state outside the relationship row. Refresh time-sensitive profiles before contacting anyone. Usernames, bios, verification, and audience counts can change. Preserve the original snapshot for review evidence. ## Compare Mutual Follower Graph Snapshots Complete every cursor page before comparing two runs. Mark a capped, credit-bounded, failed, or interrupted run as partial. Exclude partial runs from complete graph-change claims. Use acting account ID, target account ID, and mutual user ID as the key. Report newly observed and missing mutual profiles separately. Do not infer why a relationship changed. Treat username and profile changes as attributes. Stable user IDs preserve the relationship identity. Record each snapshot's collection time and page count. Keep mutual followers separate from all followers and verified followers. A source column should name `followers-you-know`. This prevents an audience warehouse from mixing distinct relationship questions. When the target changes, create a new comparison group. Never reuse cursors or relationship labels across different target user IDs. ## Path parameters Target X user ID as a numeric string. Use [Get user](/api-reference/x/twitter-profile-lookup) first if you only have a username. ## Query parameters Pagination cursor from `next_cursor` in a previous response. Omit for the first page. Pass a cursor only when `has_next_page` is true. Profiles per page. Range: `20-200`. Defaults to `200`. ## Which follower graph endpoint? Use `GET /x/users/{id}/followers-you-know` for people who follow both the authenticated context and the target user. Use [`GET /x/users/{id}/followers`](/api-reference/x/followers) for all followers of one target profile. Use [`GET /x/users/{id}/verified-followers`](/api-reference/x/verified-followers) when you only need verified followers of the target profile. Use [`Send DM`](/api-reference/x-write/send-dm) only after your workflow has a user-approved outreach step. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of mutual follower profiles. **User object fields:** User ID. X username. Display name. Profile bio. Omitted if empty. Follower count. Omitted if unavailable. Following count. Omitted if unavailable. Whether the user is verified. Omitted if unavailable. Profile picture URL. Omitted if unavailable. Profile location. Omitted if empty. ISO 8601 account creation timestamp. Omitted if unavailable. Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines. Omitted if unavailable. Whether the user is an X translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. IDs of pinned tweets. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable. Omitted if available. Reason the account is unavailable. Omitted if available. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. Role within the requested community context. Omitted outside community results. Whether more results are available. Opaque cursor for the next page. Empty string when no more results. ```json theme={null} { "users": [ { "id": "987654321", "username": "username", "name": "Xquik", "followers": 10000, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/xquik/photo.jpg" } ], "has_next_page": true, "next_cursor": "DAADDAABCgABF..." } ``` ### 400 Invalid user ID ```json theme={null} { "error": "invalid_user_id" } ``` The user ID is empty or invalid. ### 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Direct message workflow](/guides/direct-message-workflow) for user-approved outreach, [Send DM](/api-reference/x-write/send-dm) to send and store `messageId`, [DM history](/api-reference/x/dm-history) to read participant-scoped context, [Get followers](/api-reference/x/followers), [Get following](/api-reference/x/following), and [Get verified followers](/api-reference/x/verified-followers). # Twitter Following API, Profile Export & Cursors Source: https://docs.xquik.com/api-reference/x/following GET /x/users/{id}/following Retrieve the accounts one X user follows by username or numeric user ID with cursor pagination for social graph, CRM, warehouse, and agent workflows. See costs. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "Maximum coverage is busy. Retry shortly." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
## Choose Outbound Relationships Use this route for accounts the selected user follows. Use followers for inbound relationships. Use verified followers only when the inbound population must carry verification. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Get following returns the accounts one X profile follows by username or numeric user ID. It is also useful as a Following API, X following API, or Twitter following API. The canonical endpoint remains `GET /api/v1/x/users/{id}/following`. 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. 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`. 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. Finished, expired, superseded, or identity-mismatched cursors return `410 coverage_cursor_gone`. The response omits `Retry-After`. Restart without a cursor. Deduplicate restarted results by `id`. Malformed cursors return `400 invalid_coverage_cursor`. Restart without them. ```bash Username theme={null} curl "https://xquik.com/api/v1/x/users/username/following?pageSize=100" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```bash Numeric user ID theme={null} curl "https://xquik.com/api/v1/x/users/44196397/following" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```bash Resume page theme={null} curl -G "https://xquik.com/api/v1/x/users/username/following" \ --data-urlencode "cursor=DAACCgACGE..." \ --data-urlencode "pageSize=200" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const userIdOrUsername = "44196397"; let pageCursor = ""; const seenCursors = new Set(); for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { const params = new URLSearchParams({ pageSize: "200" }); if (pageCursor !== "") params.set("cursor", pageCursor); const response = await fetch( `https://xquik.com/api/v1/x/users/${userIdOrUsername}/following?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" } }, ); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const audienceRows = page.users.map((user) => ({ source_user_id_or_username: userIdOrUsername, x_user_id: user.id, x_username: user.username, display_name: user.name, bio: user.description ?? null, follower_count: user.followers ?? null, following_count: user.following ?? null, verified: user.verified ?? false, profile_image_url: user.profilePicture ?? null, page_index: pageIndex, page_cursor: pageCursor, next_cursor: page.next_cursor, has_next_page: page.has_next_page, })); for (const row of audienceRows) 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; } ``` ```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 = {"pageSize": "200"} if page_cursor: params["cursor"] = page_cursor response = requests.get( f"https://xquik.com/api/v1/x/users/{user_id_or_username}/following", params=params, headers={"x-api-key": "xq_your_api_key_here"}, ) page = response.json() if not response.ok: raise RuntimeError(page) for user in page["users"]: audience_row = { "source_user_id_or_username": user_id_or_username, "x_user_id": user["id"], "x_username": user["username"], "display_name": user["name"], "bio": user.get("description"), "follower_count": user.get("followers"), "following_count": user.get("following"), "verified": user.get("verified", False), "profile_image_url": user.get("profilePicture"), "page_index": page_index, "page_cursor": page_cursor, "next_cursor": page["next_cursor"], "has_next_page": page["has_next_page"], } print(json.dumps(audience_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"] ``` The Node.js and Python snippets write JSON Lines audience rows instead of raw following pages. Persist each mapped row and the latest `next_cursor` in your sync job so it can resume from the last completed page. For high-volume following pulls, de-duplicate profiles by `id`, continue through empty pages when the cursor advances, and stop with a partial-result status when `next_cursor` is missing or repeats. ## Direct following handoff Use `GET /x/users/{id}/following` when a CRM, warehouse, audience, or agent workflow needs one paginated JSON page of accounts followed by a user now. The endpoint accepts either a username or numeric user ID and returns followed account profile rows with cursor fields. Use [`following_explorer`](/api-reference/extractions/create) when you need an estimated job, saved extraction, or CSV/JSON/XLSX file export. Store `users[]` as the followed account profile rows returned on this page. Store `users[].id` as `x_user_id` for CRM, warehouse, audience, and agent dedupe. Store `users[].username` and `users[].name` for handles, labels, segments, and review queues. Store `users[].description`, `location`, `url`, `profilePicture`, and `coverPicture` when returned. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. Automatic pages accept 20 to 300. Standard pages accept 20 to 200. Direct following calls cost 1 credit per user returned. Low credit balances can return fewer users than a full page; zero affordable results return `402 insufficient_credits`. ## Track Twitter following changes Create complete following snapshots for the selected profile. Keep its numeric user ID and collection time with every row. Compare snapshots by followed user ID. New IDs represent newly observed outbound follows. Missing IDs represent removals only when both snapshots completed successfully. Keep the current username, profile name, biography, verification state, and follower counts for review. Do not use usernames as the comparison key. Persist every cursor page before advancing. Mark interrupted, repeated-cursor, or credit-limited runs as partial. Never calculate following changes from a partial snapshot. ## Path parameters User ID (numeric) or username. ## Query parameters Pass `next_cursor` back unchanged. New Xquik cursors resume automatic coverage. Existing unprefixed cursors keep legacy behavior. 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. Legacy cursor alias. Use `cursor`; when both are present, `cursor` wins. Automatic pages accept `20` through `300`. Standard pages accept `20` through `200`. The default is `200`. Credits can reduce the returned row count. With `mode=coverage`, set a one-shot cap from `1` through `10000`. Otherwise, this is a legacy page size alias. `pageSize` wins. ## Which following endpoint? Use `GET /x/users/{id}/following` for the accounts one profile follows. Use [`GET /x/users/{id}/followers`](/api-reference/x/followers) for the accounts that follow that profile. Use [`GET /x/users/{id}/verified-followers`](/api-reference/x/verified-followers) when you only need verified followers of the profile. Use [`following_explorer`](/api-reference/extractions/create) for a saved following extraction with CSV, JSON, or XLSX download handoff. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of user profiles being followed. **User object fields:** X user ID. X username. Display name. Profile bio. Omitted if empty. Follower count. Following count. Whether the user is verified. Profile picture URL. Profile location. Omitted if empty. ISO 8601 account creation timestamp. Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines. Omitted if unavailable. Whether the user is an X translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. IDs of pinned tweets. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable. Omitted if available. Reason the account is unavailable. Omitted if available. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. Role within the requested community context. Omitted outside community results. Whether more results are available. Cursor for the next page. ```json theme={null} { "users": [ { "id": "987654321", "username": "username", "name": "Xquik", "followers": 10000, "verified": true } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid user ID ```json theme={null} { "error": "invalid_user_id", "message": "User not found or invalid user ID. Check the username or ID." } ``` ### 404 User not found ```json theme={null} { "error": "user_not_found", "message": "X user not found. Check the username." } ``` ### 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 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Get followers](/api-reference/x/followers) · [Get verified followers](/api-reference/x/verified-followers) · [Get followers you know](/api-reference/x/followers-you-know) # X Article API for Long-Form Tweet & Post Content Source: https://docs.xquik.com/api-reference/x/get-article GET /x/articles/{tweetId} Retrieve one long-form X Article by tweet ID with title, body blocks, cover image, author profile, publication time, and engagement metrics. See costs. ```json theme={null} { "article": { "title": "The Future of AI", "previewText": "A deep dive into the latest AI trends...", "coverImageUrl": "https://pbs.twimg.com/media/example.jpg", "contents": [ { "type": "paragraph", "text": "This is the first paragraph of the article." } ], "createdAt": "2025-01-15T12:00:00Z" }, "author": { "id": "9876543210", "name": "Elon Musk", "username": "elonmusk", "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg" } } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "article_not_found", "message": "Article not found. Use an X Article tweet ID." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
**5 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Direct [MPP](/mpp/machine-payments-protocol): USD 0.00075 per call Get X Article returns one long-form X Article by wrapper tweet ID. It is also useful as an X article API, Twitter article API, tweet article API, long-form post API, or article body endpoint. The canonical endpoint remains `GET /api/v1/x/articles/{tweetId}`. ```bash Article tweet ID theme={null} curl https://xquik.com/api/v1/x/articles/2033891852621840387 \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const tweetId = "2033891852621840387"; const response = await fetch(`https://xquik.com/api/v1/x/articles/${tweetId}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const contentBlocks = data.article.contents ?? []; const author = data.author ?? {}; const bodyBlocks = contentBlocks.filter((block) => block.text); const mediaBlocks = contentBlocks.filter((block) => block.url); const formattedBlocks = contentBlocks .map((block, index) => ({ index, type: block.type ?? null, inline_style_ranges: block.inlineStyleRanges ?? [], })) .filter((block) => block.inline_style_ranges.length > 0); const handoff = { tweet_id: tweetId, article_title: data.article.title ?? null, preview_text: data.article.previewText ?? null, author_id: author.id ?? null, author_username: author.username ?? null, author_name: author.name ?? null, author_profile_picture: author.profilePicture ?? null, created_at: data.article.createdAt ?? null, cover_image_url: data.article.coverImageUrl ?? null, body_text: bodyBlocks.map((block) => block.text).join("\n\n"), block_count: contentBlocks.length, block_types: contentBlocks.map((block) => block.type ?? "unknown"), formatted_blocks: formattedBlocks, media_urls: mediaBlocks.map((block) => block.url), }; process.stdout.write(`${JSON.stringify(handoff)}\n`); ``` ```python Python theme={null} import json import requests tweet_id = "2033891852621840387" response = requests.get( f"https://xquik.com/api/v1/x/articles/{tweet_id}", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() content_blocks = data["article"].get("contents", []) body_blocks = [block for block in content_blocks if block.get("text")] media_blocks = [block for block in content_blocks if block.get("url")] formatted_blocks = [ { "index": index, "type": block.get("type"), "inline_style_ranges": block.get("inlineStyleRanges", []), } for index, block in enumerate(content_blocks) if block.get("inlineStyleRanges") ] author = data.get("author") or {} handoff = { "tweet_id": tweet_id, "article_title": data["article"].get("title"), "preview_text": data["article"].get("previewText"), "author_id": author.get("id"), "author_username": author.get("username"), "author_name": author.get("name"), "author_profile_picture": author.get("profilePicture"), "created_at": data["article"].get("createdAt"), "cover_image_url": data["article"].get("coverImageUrl"), "body_text": "\n\n".join(block["text"] for block in body_blocks), "block_count": len(content_blocks), "block_types": [block.get("type") for block in content_blocks], "formatted_blocks": formatted_blocks, "media_urls": [block["url"] for block in media_blocks], } print(json.dumps(handoff)) ``` The examples shape durable article handoff rows instead of raw lookup dumps. Use `GET /api/v1/x/articles/{tweetId}` when a workflow needs one long-form post body plus article metadata. Store `tweet_id`, `article_title`, `preview_text`, `author_id`, `author_username`, `author_name`, `author_profile_picture`, `created_at`, `cover_image_url`, and `body_text` with the downstream record. Store `block_count`, `block_types`, `formatted_blocks`, and `media_urls` when your archive, article index, or agent handoff needs block-level completeness and formatting checks. ## Find candidate articles Use this endpoint after you have the numeric wrapper tweet ID for an X Article. When a workflow starts from a mixed set of tweets, search first, store candidate tweet IDs, then try the article lookup once per candidate. Use [`Search tweets`](/api-reference/x/search-tweets) to find candidate wrapper tweets by author, keyword, URL, or visible tweet text. Call `GET /api/v1/x/articles/{tweetId}` with the candidate tweet ID when the workflow needs the long-form body. If the response is `article_not_found`, store the terminal result and switch to [`Get tweet`](/api-reference/x/get-tweet) or [`Get tweet thread`](/api-reference/x/tweet-thread). Use `article_extractor` when the workflow needs an extraction job, estimate, or CSV/JSON/XLSX export. ```json theme={null} { "content_job_id": "article-archive-q2", "candidate_source": "GET /api/v1/x/tweets/search", "article_route": "GET /api/v1/x/articles/{tweetId}", "tweet_id": "2033891852621840387", "content_type": "x_article", "article_error": null, "fallback_route": "GET /api/v1/x/tweets/{id}", "saved_fields": ["article_title", "preview_text", "body_text", "cover_image_url"] } ``` ## Direct article handoff Use `GET /api/v1/x/articles/{tweetId}` when you have the numeric tweet ID for an X Article wrapper tweet and need the article body. Use the final status ID from an X Article URL. A normal tweet ID can be valid and still return `404 article_not_found` when it is not an X Article. Store `article.title`, `previewText`, `coverImageUrl`, `createdAt`, metrics, and a derived `body_text` field. Store `article.contents[]` when you need headings, lists, quotes, media, dividers, code blocks, and inline styles. Store `author.id`, `username`, `name`, and `profilePicture` when returned. Store `coverImageUrl` and media-block `url` values for article archives and article review. Treat `article_not_found` as a terminal lookup result for that tweet ID. Ask for an X Article URL or use a tweet/thread endpoint. Use `article_extractor` when you need a saved extraction job or CSV, JSON, or XLSX export. ## Store an X Article archive Preserve the wrapper Tweet ID, article fields, structured content blocks, author, cover image, and collection time. Keep `article.contents[]` when the archive must reconstruct headings, lists, quotes, media, dividers, or code. | Article archive column | Response source | Archive rule | | ---------------------- | --------------------------------- | --------------------------------------------------------------- | | `wrapper_tweet_id` | Requested `tweetId` | Use as the stable article lookup key. | | `article_title` | `article.title` | Preserve the returned X Article title. | | `preview_text` | `article.previewText` | Store the short article preview when returned. | | `body_text` | Derived from `article.contents[]` | Build searchable text without discarding structured blocks. | | `content_blocks` | `article.contents[]` | Preserve block order, type, text, styles, and media references. | | `cover_image_url` | `article.coverImageUrl` | Keep the article cover asset with its source record. | | `author_id` | `author.id` | Use as the stable article-author key. | | `author_username` | `author.username` | Display the current author handle. | | `created_at` | `article.createdAt` | Preserve the article publication time. | | `media_urls` | Media block URLs | Keep inline images or videos with their block positions. | | `collected_at` | Integration timestamp | Audit when the article body was retrieved. | | Article block | Text projection | Structured value to preserve | | ------------- | ------------------- | ---------------------------------- | | Heading | Heading text | Heading level and inline styles | | Paragraph | Paragraph text | Inline links, mentions, and styles | | List | Ordered item text | List type, item order, and nesting | | Quote | Quoted text | Quote attribution when returned | | Media | Alt text or caption | Media URL, type, and position | | Code | Code text | Language and formatting metadata | | Divider | No body text | Block position in the article | Direct article reads cost 5 credits per successful call. For MPP callers, this endpoint is billed as a fixed charge at USD 0.00075 per call. ## Path parameters Numeric tweet ID of the X Article, 15-20 digits. If you have a tweet URL, use the final status ID. Regular tweet URLs can return `article_not_found`. ## Which article endpoint? Use `GET /x/articles/{tweetId}` for the title, body blocks, cover image, metrics, and author fields of one X Article. Use [`Get tweet`](/api-reference/x/get-tweet) for one tweet's text, media, author, and engagement metrics. Use [`Get tweet thread`](/api-reference/x/tweet-thread) for conversation context around the article wrapper tweet. Use [`Search tweets`](/api-reference/x/search-tweets) to discover candidate article tweets by keyword, URL, author, or other filters. Use [`Create extraction`](/api-reference/extractions/create) with `toolType=article_extractor` when you need a saved job or CSV, JSON, or XLSX export. Do not retry the same ID after `article_not_found`; switch to tweet lookup or ask for an X Article URL. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` authenticates `paid_reads` guest keys. Direct MPP uses the `Payment ...` credential. Get it from the `WWW-Authenticate: Payment` challenge. ## Response ### 200 OK The article data. **Article object fields:** Article title. Short preview/summary text of the article. Cover thumbnail image URL. Plain text joined from all article content blocks. Omitted if unavailable. Article body as an array of content blocks. **Content block fields:** Block type: `paragraph`, `header-one`, `header-two`, `header-three`, `header-four`, `header-five`, `header-six`, `ordered-list-item`, `unordered-list-item`, `blockquote`, `code-block`, `media`, or `divider`. Text content for text-based blocks. Media URL for `media` blocks. Preview image URL for `media` blocks. Image width in pixels. Image height in pixels. Inline text formatting. **Style range fields:** Character offset where the style starts. Number of characters the style spans. Draft.js style token such as `BOLD` or `ITALIC`. Article creation timestamp. Like count. Reply count. Quote tweet count. View count. The article author. Omitted if author data is unavailable. **Author object fields:** Author user ID. Author X username. Author display name. Profile picture URL. Omitted if unavailable. Author bio. Omitted if unavailable. Profile location. Omitted if unavailable. Profile website URL. Omitted if unavailable. Account creation timestamp. Omitted if unavailable. Follower count. Omitted if unavailable. Following count. Omitted if unavailable. Posted tweet count. Omitted if unavailable. Media post count. Omitted if unavailable. Liked tweet count. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Whether the account is an X translator. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. ```json theme={null} { "article": { "title": "Why Your TikTok & Instagram Videos Aren't Getting Views", "previewText": "Creating organic videos for Instagram Reels and TikTok is one of the most effective ways to attract customers...", "coverImageUrl": "https://pbs.twimg.com/media/HDcia3lXsAASgbQ.jpg", "contents": [ { "type": "paragraph", "text": "Creating organic videos for Instagram Reels and TikTok is one of the most effective ways to attract customers." }, { "type": "header-two", "text": "The problem" }, { "type": "ordered-list-item", "text": "Your account isn't properly set up" }, { "type": "media", "url": "https://pbs.twimg.com/media/HDm1eekbQAAzP9c.png", "previewUrl": "https://pbs.twimg.com/media/HDm1eekbQAAzP9c.png", "width": 640, "height": 804 }, { "type": "paragraph", "text": "What really matters is the hook.", "inlineStyleRanges": [ { "offset": 23, "length": 8, "style": "BOLD" } ] } ], "createdAt": "Tue Mar 17 13:03:00 +0000 2026", "likeCount": 156, "replyCount": 9, "quoteCount": 4, "viewCount": 71303 }, "author": { "id": "1857516996755165184", "username": "cesaralvarezll", "name": "César Álvarez", "profilePicture": "https://pbs.twimg.com/profile_images/1884934857567895553/hHWR_iBg_normal.jpg" } } ``` ### 400 Invalid tweet ID ```json theme={null} { "error": "invalid_tweet_id" } ``` The provided tweet ID is empty or not a valid format. ### 401 Unauthenticated ```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. Anonymous calls receive a direct MPP `WWW-Authenticate: Payment` challenge plus a guest wallet creation action. No checkout starts automatically. Confirm any payment action. ### 404 Article not found ```json theme={null} { "error": "article_not_found", "message": "Article not found. Use an X Article tweet ID." } ``` The tweet is valid but does not contain an X Article. ### 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Get tweet](/api-reference/x/get-tweet) · [Get tweet thread](/api-reference/x/tweet-thread) · [Search tweets](/api-reference/x/search-tweets) · [Create extraction](/api-reference/extractions/create) # Tweet Lookup API, Post Details & Engagement Counts Source: https://docs.xquik.com/api-reference/x/get-tweet GET /x/tweets/{id} Retrieve one tweet by numeric ID with full text, author profile, media, reply and quote context, likes, reposts, views, and URLs. See response fields. ```json theme={null} { "tweet": { "id": "1234567890", "text": "Just launched our new feature!", "createdAt": "2025-01-15T12:00:00Z", "retweetCount": 5, "replyCount": 3 }, "author": { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk", "followers": 150000000, "verified": true } } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "tweet_not_found", "message": "Tweet not found. Check the tweet ID." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
**1 credit per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Direct [MPP](/mpp/machine-payments-protocol): USD 0.00015 per call Get tweet returns one tweet by numeric ID. It is also useful as a tweet lookup API, single tweet API, tweet info API, X tweet API, or tweet details endpoint. The canonical endpoint remains `GET /api/v1/x/tweets/{id}`. See [Read Data Richness](/guides/tweet-profile-api-fields) for every optional tweet, author, and media field. ```bash Tweet ID theme={null} curl https://xquik.com/api/v1/x/tweets/1893456789012345678 \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const tweetId = "1893456789012345678"; const response = await fetch(`https://xquik.com/api/v1/x/tweets/${tweetId}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const tweet = data.tweet; const author = data.author ?? {}; const media = tweet.media ?? []; const quotedTweet = tweet.quoted_tweet; const handoff = { tweet_id: tweet.id, text: tweet.text, author_id: author.id ?? null, author_username: author.username ?? 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, is_reply: tweet.isReply === true, is_quote_status: tweet.isQuoteStatus === true, is_note_tweet: tweet.isNoteTweet === true, tweet_source: tweet.source ?? null, quote_tweet_id: quotedTweet?.id ?? null, metrics: { retweets: tweet.retweetCount ?? 0, replies: tweet.replyCount ?? 0, likes: tweet.likeCount ?? 0, quotes: tweet.quoteCount ?? 0, views: tweet.viewCount ?? 0, bookmarks: tweet.bookmarkCount ?? 0, }, media_urls: media.map((item) => item.mediaUrl), }; process.stdout.write(`${JSON.stringify(handoff)}\n`); ``` ```python Python theme={null} import json import requests tweet_id = "1893456789012345678" response = requests.get( f"https://xquik.com/api/v1/x/tweets/{tweet_id}", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() tweet = data["tweet"] author = data.get("author") or {} media = tweet.get("media", []) quoted_tweet = tweet.get("quoted_tweet") or {} handoff = { "tweet_id": tweet["id"], "text": tweet["text"], "author_id": author.get("id"), "author_username": author.get("username"), "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"), "is_reply": tweet.get("isReply") is True, "is_quote_status": tweet.get("isQuoteStatus") is True, "is_note_tweet": tweet.get("isNoteTweet") is True, "tweet_source": tweet.get("source"), "quote_tweet_id": quoted_tweet.get("id"), "metrics": { "retweets": tweet.get("retweetCount", 0), "replies": tweet.get("replyCount", 0), "likes": tweet.get("likeCount", 0), "quotes": tweet.get("quoteCount", 0), "views": tweet.get("viewCount", 0), "bookmarks": tweet.get("bookmarkCount", 0), }, "media_urls": [item["mediaUrl"] for item in media], } print(json.dumps(handoff)) ``` The examples shape durable tweet lookup rows instead of raw response dumps. Use `GET /api/v1/x/tweets/{id}` when a workflow needs one tweet plus author context. Store `tweet_id`, `text`, `author_id`, `author_username`, `author_followers`, `author_verified`, `author_profile_picture`, `created_at`, `conversation_id`, `is_reply`, `is_quote_status`, `is_note_tweet`, `tweet_source`, `quote_tweet_id`, `metrics`, and `media_urls` with the downstream record. ## Direct tweet handoff Pass a 15 to 20 digit numeric tweet ID in the path. If a user gives a tweet URL, extract the final status ID first. For workflows that accept pasted Tweet URLs, call [`Search tweets`](/api-reference/x/search-tweets) with the URL in `q` and omit `cursor`, `sinceTime`, and `untilTime`. URLs, usernames, and short IDs still return `400 invalid_tweet_id` on this path endpoint. Use the response when you need normalized tweet text, optional author data, engagement counts, quote metadata, conversation context, Note Tweet text, source, and media URLs for 1 record. Store one durable row per ID with `tweet_id`, `text`, timestamps, flags, metrics, and media URLs. Use embedded `author` fields when returned instead of making a second user lookup for the same row. Store `isQuoteStatus` and `quoted_tweet.id` when you need quote joins or attribution. Store `conversationId` and `isReply` when the row feeds a thread, reply, or moderation workflow. Use `isNoteTweet` and `tweet.text` for complete Note Tweet text returned by this endpoint. Store each `media[].mediaUrl` for review queues, warehouses, and downstream enrichment. ## Store a tweet lookup record Use the numeric Tweet ID as the stable key. Keep text, author, thread, quote, engagement, media, and disclosure fields as separate columns instead of flattening the response into one unsearchable value. | Tweet record column | Response source | Handoff rule | | -------------------- | ------------------------- | --------------------------------------------------------- | | `tweet_id` | `tweet.id` | Use as the stable tweet upsert key. | | `tweet_url` | `tweet.url` | Preserve the original X permalink when returned. | | `text` | `tweet.text` | Store the complete returned tweet or Note Tweet text. | | `created_at` | `tweet.createdAt` | Preserve the tweet publication time. | | `author_id` | `author.id` | Keep a stable author identity when usernames change. | | `author_username` | `author.username` | Display the current author handle. | | `conversation_id` | `tweet.conversationId` | Join the tweet to its conversation thread. | | `in_reply_to_id` | `tweet.inReplyToId` | Join replies to their immediate parent tweet. | | `quote_tweet_id` | `tweet.quoted_tweet.id` | Preserve quoted-tweet attribution when present. | | `is_note_tweet` | `tweet.isNoteTweet` | Distinguish long-form Note Tweet text. | | `like_count` | `tweet.likeCount` | Store the observed engagement count with lookup time. | | `reply_count` | `tweet.replyCount` | Route active conversation threads for review. | | `media_urls` | `tweet.media[].mediaUrl` | Preserve image, video, or animated GIF URLs. | | `content_disclosure` | `tweet.contentDisclosure` | Preserve paid-promotion or AI-media labels when returned. | Direct tweet reads cost 1 credit per successful call. For MPP callers, this endpoint is billed as a fixed charge at USD 0.00015 per call. ## Path parameters Numeric tweet ID, 15-20 digits. If you have a tweet URL, use the final status ID. ## Which tweet endpoint? Use `GET /x/tweets/{id}` for one tweet's text, author, media, quote or reply flags, metrics, and Note Tweet text. Use [`Get tweets (batch)`](/api-reference/x/batch-tweets) when you already have multiple numeric IDs. Use [`Search tweets`](/api-reference/x/search-tweets) when you need keyword, operator, author, date, media, engagement, verification filters, or pasted Tweet URL exact lookup. Use [`Get tweet thread`](/api-reference/x/tweet-thread) when the next action needs surrounding conversation rows. Use replies, quote tweets, retweeters, or favoriters pages when you need users or tweets connected to this tweet. Use [`Create extraction`](/api-reference/extractions/create) with `reply_extractor`, `quote_extractor`, `repost_extractor`, `thread_extractor`, or `tweet_search_extractor` when you need CSV, JSON, or XLSX output. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` authenticates `paid_reads` guest keys. Direct MPP uses the `Payment ...` credential. Get it from the `WWW-Authenticate: Payment` challenge. ## Response ### 200 OK The tweet data. **Tweet object fields:** Tweet ID. Tweet text content. For Note Tweets (long-form posts), returns the complete text up to 25,000 characters. ISO 8601 creation timestamp. Whether this is a Note Tweet (long-form post, up to 25,000 characters). Omitted when false. Whether this tweet is a reply to another tweet. Omitted when false. Whether X limits who can reply. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted when false. ID of the root tweet in the conversation thread. Tweet ID this post replies to. Omitted when not a reply. User ID this post replies to. Omitted when unavailable. Username this post replies to. Omitted when unavailable. Client application used to post this tweet. Tweet type. Omitted if unavailable. Tweet permalink. Omitted if unavailable. Tweet language code. Omitted if unavailable. Start and end offsets for rendered text. Omitted if unavailable. Parsed entities from the tweet text (URLs, mentions, hashtags, media). Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable. The quoted tweet object. Present when `isQuoteStatus` is true. The original tweet when this post is a repost. Omitted otherwise. Retweet count. Reply count. Like count. Quote tweet count. View count. Bookmark count. Attached media items. Omitted when the tweet has no attached media. **Media item fields:** Direct media URL (pbs.twimg.com). Available video renditions with bitrate, content type, and URL. Omitted for images. Media type: `photo`, `video`, or `animated_gif`. Shortened t.co URL from the tweet text. The tweet author. Omitted if author data is unavailable. **Author object fields:** Author fields can include `id`, `username`, `name`, `followers`, `verified`, and `profilePicture` when available. ```json theme={null} { "tweet": { "id": "1893456789012345678", "text": "Introducing our new extraction API. Ship faster.", "createdAt": "2026-02-24T14:30:00.000Z", "isNoteTweet": false, "isReply": false, "isQuoteStatus": true, "conversationId": "1893456789012345678", "source": "Twitter Web App", "contentDisclosure": { "advertising": { "isPaidPromotion": true }, "aiGenerated": { "detectionSource": "UserDeclared", "hasAiGeneratedMedia": true } }, "entities": { "urls": [ { "display_url": "xquik.com/blog/extracti...", "expanded_url": "https://xquik.com/blog/extraction-api", "url": "https://t.co/abc123" } ], "hashtags": [], "user_mentions": [] }, "quoted_tweet": { "id": "1893000000000000000", "text": "What API tools are you shipping this week?", "author": { "id": "111222333", "username": "devtools" } }, "retweetCount": 320, "replyCount": 85, "likeCount": 1400, "quoteCount": 45, "viewCount": 250000, "bookmarkCount": 210, "media": [ { "mediaUrl": "https://pbs.twimg.com/media/example.jpg", "type": "photo", "url": "https://t.co/abc123" } ] }, "author": { "id": "987654321", "username": "username", "followers": 10000, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/xquik/photo.jpg" } } ``` ### 400 Invalid tweet ID ```json theme={null} { "error": "invalid_tweet_id" } ``` The provided tweet ID is empty or not a valid format. ### 401 Unauthenticated ```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. Anonymous calls receive a direct MPP `WWW-Authenticate: Payment` challenge plus a guest wallet creation action. No checkout starts automatically. Confirm any payment action. ### 404 Tweet not found ```json theme={null} { "error": "tweet_not_found" } ``` The tweet does not exist. It may have been deleted or the ID is invalid. ### 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 } ``` Your tier rate limit was exceeded. 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. **Next steps:** [Search Tweets](/api-reference/x/search-tweets) to find tweets by query, or [Get User](/api-reference/x/twitter-profile-lookup) to look up the author profile. # Twitter List Followers API & Profile Export Source: https://docs.xquik.com/api-reference/x/list-followers GET /x/lists/{id}/followers Retrieve Twitter List followers with usernames, bios, locations, profile images, verification, follower counts, following counts, and JSON cursor pages. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Use this Twitter List followers API for one X List. It returns subscriber profiles. Export user IDs, usernames, bios, and profile locations. Keep verification, follower counts, following counts, profile images, and cursors. ## Twitter List Followers Questions ### What Is a Twitter List Follower? A List follower subscribes to one List. A List member is an account selected by the List owner. An account follower follows one profile instead. X says anyone can follow a public List. Only its owner can access a private List. Read X's official [Lists guide](https://help.x.com/en/using-x/x-lists) for current List visibility and follow behavior. ### How Do I Find Who Follows a Specific Twitter List? Copy the numeric List ID from its X URL. Request the first page without a cursor. Store every returned user row before requesting another page. Xquik returns `has_next_page` and `next_cursor`. Continue only when `has_next_page` is true. Pass `next_cursor` unchanged as the next `cursor`. X also documents a List follower route. Its official [Get List followers](https://docs.x.com/x-api/lists/get-list-followers) guide uses different pagination field names. ### How Do I Export Twitter List Followers? The direct endpoint returns JSON pages with named fields. Convert each profile into one row. Store the List ID, user ID, username, bio, counts, and request time. Use `list_follower_explorer` for a saved Twitter List follower export. The job can produce CSV, JSON, or XLSX files without custom spreadsheet code. ### What Can I Check in Each Twitter Profile? Check user IDs, usernames, bios, and public location text. Check when each account began, its verified status, and its public counts. Check automated, protected, and unavailable flags only when the response includes them. These fields do not show age, gender, income, identity, consent, or sentiment. A profile location does not prove where someone lives. ### Can This API Add, Remove, or Buy List Followers? No. This read endpoint cannot follow a List or remove a List follower. It cannot create Lists, manage List members, publish posts, or buy followers. Use the [List Members endpoint](/api-reference/x/list-members) for the curated profile roster. That endpoint also reads profiles. It does not edit the List. 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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl "https://xquik.com/api/v1/x/lists/1234567890/followers" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const listId = "1234567890"; const response = await fetch(`https://xquik.com/api/v1/x/lists/${listId}/followers`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const followerRows = data.users.map((user) => ({ list_id: listId, follower_id: user.id, username: user.username, display_name: user.name, bio: user.description ?? null, follower_count: user.followers ?? null, following_count: user.following ?? null, verified: user.verified ?? false, profile_image_url: user.profilePicture ?? null, })); const nextCursor = data.has_next_page ? data.next_cursor : null; ``` ```python Python theme={null} import requests list_id = "1234567890" response = requests.get( f"https://xquik.com/api/v1/x/lists/{list_id}/followers", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() follower_rows = [ { "list_id": list_id, "follower_id": user["id"], "username": user["username"], "display_name": user["name"], "bio": user.get("description"), "follower_count": user.get("followers"), "following_count": user.get("following"), "verified": user.get("verified", False), "profile_image_url": user.get("profilePicture"), } for user in data["users"] ] next_cursor = data["next_cursor"] if data["has_next_page"] else None ``` The Node.js and Python snippets build one row per List follower. Save `followerRows` or `follower_rows` with `nextCursor`. Then request the next page. ## Direct List Follower Handoff Use `GET /x/lists/{id}/followers` for one JSON page of accounts that follow a List. Send the saved rows to a CRM, warehouse, audience tool, or agent. Use [`list_follower_explorer`](/api-reference/extractions/create) for a saved job and CSV, JSON, or XLSX export. Store `list_id`, `follower_id`, `username`, and `display_name`. Keep profile counts, verified state, `has_next_page`, and `next_cursor`. Do not edit the cursor. Pass it back only when `has_next_page` is true. Direct calls use the default paid page size. Each returned user costs 1 credit. If credits cover zero results, Xquik returns `402 insufficient_credits`. Page accounts that follow the list. Store the row shape above for CRM, warehouse, and audience imports. Store `has_next_page` and `next_cursor`. Only request another page when `has_next_page` is true. Direct calls use the default paid page size. Treat the returned `users.length` as the row count returned for this page. Use `list_follower_explorer` when the workflow needs a saved job with CSV/JSON/XLSX output. ## Measure Twitter List Follower Profiles Use List followers for profiles that chose to follow one List. Keep the List ID with every profile. This keeps follower rows apart from List member rows. Save these fields: * User ID, username, and profile name. * Bio, location, and verified state. * Follower and following counts. * List ID, cursor, and collection time. Compare List follower snapshots by user ID. Record new and missing IDs on separate lines. A new username does not mean a new follower. Use list members when you need the list owner’s curated roster. Use account followers when you need the audience of one profile. These relationship types answer different questions. For outreach review, keep source list context. Never infer consent or contact permission from a public follow relationship. ## Track List Audience Changes Finish both follower snapshots before finding added or missing IDs. Record the List ID, request time, page count, and final row count. Use follower user IDs as stable match keys. Keep usernames as labels. Mark stopped or credit-limited jobs as partial. A snapshot diff shows the List followers seen in each run. It does not show changes to the List's member roster. Report newly observed follower IDs separately from missing IDs. Keep both snapshot completion states beside the comparison. Use snapshot differences to review the audience only. A public follow does not grant outreach rights or prove interest in every List topic. ## Path Parameters List ID (numeric string). ## Query Parameters Pagination cursor from a previous response. Omit for the first page. Profiles per page. Range: `20-200`. Defaults to `200`. ## Which List Endpoint? Use `GET /x/lists/{id}/followers` for accounts that follow the list. Use [`GET /x/lists/{id}/members`](/api-reference/x/list-members) for accounts the list owner added to the list. Use [`GET /x/lists/{id}/tweets`](/api-reference/x/list-tweets) for tweets from accounts in the list. Use [`Create extraction`](/api-reference/extractions/create) with `list_follower_explorer`, `list_member_extractor`, or `list_post_extractor` when the workflow needs a saved export. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of list followers. **User object fields:** User ID. X username. Display name. Contains the profile bio. Shows how many accounts follow this profile. Shows how many accounts this profile follows. Shows if X marks this profile as verified. Links to the profile image. Shows the location written on the profile. Shows when the account began, in ISO 8601 format. Shows the post count if X returns it. Links to the cover image if X returns it. Shows the media post count if X returns it. Website URL from profile. Omitted if empty. Shows how many posts this account liked if X returns it. Shows custom timelines if X returns this field. Shows X translator status if X returns it. Lists country codes where X withholds this account. Omitted if empty. Shows if X marks the account as sensitive. Omitted if X does not send it. Lists pinned Tweet IDs. Omitted if none. Shows if X marks this account as automated. Omitted if X does not send it. Names the operator. Omitted when the account is not automated. Shows if X could not load the account. Omitted when X loads it. Explains why X could not load the account. Omitted when X loads it. Shows `Business` or `Government` status. Omitted for blue checks or unverified profiles. Adds the structured bio and its tags. Omitted if X does not send it. Shows if X Premium verifies the account. Omitted if X does not send it. Shows the normalized verified state. Omitted if X does not send it. Links to the profile banner. Omitted if X does not send it. Shows if the account protects its posts. Omitted if X does not send it. Shows a community role only in community results. Shows if another page exists. Gives the next cursor. Pass it as the `cursor` query value. ```json theme={null} { "users": [ { "id": "987654321", "username": "username", "name": "Xquik", "followers": 10000, "verified": true } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid List ID ```json theme={null} { "error": "invalid_list_id", "message": "List ID required" } ``` The list ID path parameter is empty. ### 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. ### 404 List Not Found ```json theme={null} { "error": "not_found" } ``` The list could not be resolved. Check the list ID. ### 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's rate limit. Wait for the `Retry-After` header before retrying. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` The v1 response can return 424 when the read service fails. **Related:** [List Members](/api-reference/x/list-members) · [List Tweets](/api-reference/x/list-tweets) # Twitter List Members API & CSV Profile Export Guide Source: https://docs.xquik.com/api-reference/x/list-members GET /x/lists/{id}/members Retrieve Twitter List members with usernames, bios, verification, profile images, and follower counts. Export pages as CSV, JSON, or XLSX for roster analysis. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Use this Twitter List members API for one X List. Retrieve its curated profiles. Keep user IDs, usernames, bios, verification, profile images, and public counts. Follow every cursor to export Twitter List members into a complete roster. ## Twitter List Members Questions ### What Is a Twitter List Member? A List member is an account selected by the List owner. Membership creates the List's curated timeline. It does not mean the account follows that List. An account follower follows one profile instead. X explains these roles in its official [Lists guide](https://help.x.com/en/using-x/x-lists). ### How Do I View Members of a Twitter List? Copy the numeric List ID from its X URL. Request the first page without a cursor. Store every returned profile before requesting another page. Continue only when `has_next_page` is true. Pass `next_cursor` unchanged as `cursor`. X also documents a [Get List members](https://docs.x.com/x-api/lists/get-list-members) route. Its response and pagination fields differ from Xquik's fields. ### How Do I Export Twitter List Members? The direct endpoint returns JSON pages. Convert each profile into one export row. Store the List ID, member ID, username, bio, counts, and collection time. Follow every cursor before treating the export as complete. Use `list_member_extractor` for a saved job. It can produce CSV, JSON, or XLSX files without custom spreadsheet code. ### Which Profile Fields Can I Analyze? Analyze user IDs, usernames, display names, bios, and public location text. Compare follower counts, following counts, verification, and account dates. Use public counts to sort a roster for manual review. Do not call a high count "influence" without a clear method. This endpoint does not calculate reach, engagement, demographics, sentiment, or audience quality. ### How Do I Compare List Member Snapshots? Finish both snapshots before comparing their member IDs. Added IDs indicate newly observed members. Missing IDs indicate newly unobserved members. Record both snapshot times and completion states. A renamed username is not a new member. Never report removals from an incomplete later snapshot. ### Can I Export Members From a Private Twitter List? Access depends on the credentials and visibility available to the read service. Never assume a private List is readable. Treat `404` as unavailable unless the List ID is wrong. Do not promise access to private Lists. Public profile fields also do not grant outreach consent. ### Can This Endpoint Add or Remove List Members? No. This GET endpoint only reads the current roster. It cannot create Lists, add members, remove members, follow Lists, or publish tweets. Use X's supported write tools for owner-authorized changes. Xquik does not expose those actions through this endpoint. ### How Do I Find Active Members or Recent Tweets? This response describes profiles, not recent activity. Retrieve [List Tweets](/api-reference/x/list-tweets) for posts from the current List timeline. Analyze tweets separately from List membership. A profile count does not prove recent activity. One active account can publish many posts while remaining one List member. 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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl "https://xquik.com/api/v1/x/lists/1234567890/members" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const listId = "1234567890"; const response = await fetch(`https://xquik.com/api/v1/x/lists/${listId}/members`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const memberRows = data.users.map((user) => ({ list_id: listId, member_id: user.id, username: user.username, display_name: user.name, bio: user.description ?? null, follower_count: user.followers ?? null, following_count: user.following ?? null, verified: user.verified ?? false, profile_image_url: user.profilePicture ?? null, })); const nextCursor = data.has_next_page ? data.next_cursor : null; ``` ```python Python theme={null} import requests list_id = "1234567890" response = requests.get( f"https://xquik.com/api/v1/x/lists/{list_id}/members", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() member_rows = [ { "list_id": list_id, "member_id": user["id"], "username": user["username"], "display_name": user["name"], "bio": user.get("description"), "follower_count": user.get("followers"), "following_count": user.get("following"), "verified": user.get("verified", False), "profile_image_url": user.get("profilePicture"), } for user in data["users"] ] next_cursor = data["next_cursor"] if data["has_next_page"] else None ``` The Node.js and Python snippets create one row per List member. Save `memberRows` or `member_rows` with `nextCursor`. Then request the next page. ## Direct List Member Handoff Use `GET /x/lists/{id}/members` for one JSON page of curated accounts. Send saved rows to a CRM, warehouse, audience tool, or agent. Use [`list_member_extractor`](/api-reference/extractions/create) for a saved job. That job supports CSV, JSON, or XLSX file export. Store `list_id`, `member_id`, `username`, and `display_name`. Keep profile counts, verification, `has_next_page`, and `next_cursor`. Do not edit the cursor. Pass it back only when `has_next_page` is true. Direct calls accept a `pageSize` from 20 through 200. Each returned profile costs 1 credit. Xquik returns `402 insufficient_credits` when credits cover zero results. Page accounts the list owner curated as members. Use the row shape above for CRM, warehouse, and audience imports. Store `has_next_page` and `next_cursor`. Only request another page when `has_next_page` is true. Set `pageSize` from 20 to 200. Treat the returned `users.length` as the row count for the page. Use `list_member_extractor` when the workflow needs a saved job with CSV/JSON/XLSX output. ## Export a Curated List Roster Use List members for profiles selected by one List owner. Keep the List ID on every row. Also keep the collection time and snapshot status. Useful list-member columns include: * User ID, username, and profile name. * Biography, location, and verification state. * Follower and following counts. * List ID and collection timestamp. Compare snapshots by user ID. Profile owners can rename their usernames. List membership reflects curation, not audience interest. Use List followers for profiles that follow the List. Use account followers for one profile's audience. For CRM imports, deduplicate by List ID and user ID. This preserves the same profile across several Lists. ## Track List Curation Changes Create complete snapshots at consistent intervals. Mark added user IDs as newly observed members. Mark missing IDs only after a complete later snapshot. Keep the list name and owner in your snapshot metadata. The endpoint path uses the list ID, while reviewers often recognize the readable name. Use snapshot differences to review curation decisions. Do not call them follower growth. The List owner controls membership. Create one change row for each added or removed user ID. Include the earlier and later snapshot times. Keep the current username only as a display label. Send uncertain comparisons to review. Never publish incomplete comparisons. ## Path Parameters List ID (numeric string). ## Query Parameters Pagination cursor from a previous response. Omit for the first page. Results per page. Range: 20-200. Default: `20`. ## Which List Endpoint? Use `GET /x/lists/{id}/members` for accounts the list owner added to the list. Use [`GET /x/lists/{id}/followers`](/api-reference/x/list-followers) for accounts that follow the list. Use [`GET /x/lists/{id}/tweets`](/api-reference/x/list-tweets) for tweets from accounts in the list. Use [`Create extraction`](/api-reference/extractions/create) with `list_member_extractor`, `list_follower_explorer`, or `list_post_extractor` when the workflow needs a saved export. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of list members. **User object fields:** User ID. X username. Display name. Contains the profile bio. Shows how many accounts follow this profile. Shows how many accounts this profile follows. Shows if X marks this profile as verified. Links to the profile image. Shows the location written on the profile. Shows when the account began, in ISO 8601 format. Shows the post count if X returns it. Links to the cover image if X returns it. Shows the media post count if X returns it. Website URL from profile. Omitted if empty. Shows how many posts this account liked if X returns it. Shows custom timelines if X returns this field. Shows X translator status if X returns it. Lists country codes where X withholds this account. Omitted if empty. Shows if X marks the account as sensitive. Omitted if X does not send it. Lists pinned Tweet IDs. Omitted if none. Shows if X marks this account as automated. Omitted if X does not send it. Names the operator. Omitted when the account is not automated. Shows if X could not load the account. Omitted when X loads it. Explains why X could not load the account. Omitted when X loads it. Shows Business or Government status. Omitted for blue checks or unverified profiles. Adds the structured bio and its tags. Omitted if X does not send it. Shows if X Premium verifies the account. Omitted if X does not send it. Shows the normalized verified state. Omitted if X does not send it. Links to the profile banner. Omitted if X does not send it. Shows if the account protects its posts. Omitted if X does not send it. Shows a community role only in community results. Shows if another page exists. Gives the next cursor. Pass it as the `cursor` query value. ```json theme={null} { "users": [ { "id": "987654321", "username": "username", "name": "Xquik", "followers": 10000, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/xquik/photo.jpg" } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid List ID ```json theme={null} { "error": "invalid_list_id", "message": "List ID required" } ``` The list ID path parameter is empty. ### 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. ### 404 List Not Found ```json theme={null} { "error": "not_found" } ``` The list could not be resolved. Check the list ID. ### 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's rate limit. Wait for the `Retry-After` header. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` The v1 response can return 424 when the read service fails. **Related:** [List Followers](/api-reference/x/list-followers) · [List Tweets](/api-reference/x/list-tweets) # Twitter List Tweets API & Timeline Export Guide Source: https://docs.xquik.com/api-reference/x/list-tweets GET /x/lists/{id}/tweets Retrieve tweets from an X list with authors, text, replies, reposts, likes, quotes, media, timestamps, and cursor-based pagination. See request fields. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
Use this Twitter Lists API to retrieve Twitter List tweets from one curated timeline. Export Tweet text, authors, replies, reposts, likes, quotes, media, and cursor pages. Provide the numeric List ID from its X URL. ## Twitter List Tweets Questions ### What Does the Twitter Lists API Return? The endpoint returns posts from accounts in one X List timeline. Each page can include Tweet text, author profiles, timestamps, engagement counts, media, and pagination fields. The response represents one observed timeline window. It does not prove a complete archive or permanent List membership. ### How Do I Export Tweets From a Twitter List? Request the first page with the numeric List ID. Normalize each Tweet into one row. Store `has_next_page` beside `next_cursor` before requesting another page. Use `sinceTime` and `untilTime` for a bounded collection window. Use `list_post_extractor` for a saved CSV, JSON, or XLSX export. ### Why Are Some Twitter List Tweets Missing? First, check `includeReplies`. The default excludes replies. Then verify any `sinceTime` and `untilTime` boundaries. Visibility also depends on the connected read context. X says protected posts remain visible only to approved followers. Private Lists owned by other accounts are not visible. Read X's official [Help with Lists](https://help.x.com/en/using-x/x-lists-not-working) for current visibility rules. Remaining credits can reduce a paid page. New posts can also shift a moving timeline between requests. A missing Tweet does not prove deletion. ### Can This Endpoint Create or Edit a Twitter List? No. This route only reads List tweets. It cannot create a public List or private List. It cannot add members, remove members, follow Lists, or publish posts. Use X's [Lists guide](https://help.x.com/en/using-x/x-lists) to create and manage Lists. Use the [List Members endpoint](/api-reference/x/list-members) for the current profile roster. ### How Do I Measure Activity in a Curated List Timeline? Group rows by stable author ID. Count Tweets, replies, reposts, likes, quotes, views, and media items separately. Store every count with its collection time. These measures describe captured List tweets. They do not expose unique viewers, link clicks, conversions, or audience sentiment. ### Does the API Preserve List Tweet Order? Preserve the returned Tweet sequence in every page. Do not treat that order as permanent. New posts and membership changes can shift later requests. Record collection time, List ID, and cursor with every page. Deduplicate retries by stable Tweet ID without re-ranking the saved rows. 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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl "https://xquik.com/api/v1/x/lists/1234567890/tweets" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const listId = "1234567890"; const response = await fetch(`https://xquik.com/api/v1/x/lists/${listId}/tweets`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const tweetRows = data.tweets.map((tweet) => ({ list_id: listId, 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, media_urls: tweet.media?.map((item) => item.mediaUrl).filter(Boolean) ?? [], })); const nextCursor = data.has_next_page ? data.next_cursor : null; for (const row of tweetRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } if (nextCursor !== null) { process.stdout.write(`${JSON.stringify({ list_id: listId, next_cursor: nextCursor })}\n`); } ``` ```python Python theme={null} import json import requests list_id = "1234567890" response = requests.get( f"https://xquik.com/api/v1/x/lists/{list_id}/tweets", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() tweet_rows = [ { "list_id": list_id, "tweet_id": tweet["id"], "text": tweet.get("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"), "like_count": tweet.get("likeCount"), "reply_count": tweet.get("replyCount"), "retweet_count": tweet.get("retweetCount"), "media_urls": [ item["mediaUrl"] for item in tweet.get("media", []) if item.get("mediaUrl") ], } for tweet in data["tweets"] ] next_cursor = data["next_cursor"] if data["has_next_page"] else None for row in tweet_rows: print(json.dumps(row)) if next_cursor is not None: print(json.dumps({"list_id": list_id, "next_cursor": next_cursor})) ``` The Node.js & Python snippets shape one durable row per returned list tweet instead of printing the full response page. Persist the final `next_cursor` row when `has_next_page` is true, then pass it back as `cursor` for the next page. ## Direct List Tweet Handoff Use `GET /x/lists/{id}/tweets` when a CRM, warehouse, newsroom, monitoring job, or agent needs tweets from a curated X List. Store `list_id`, `tweet_id`, `text`, `author_id`, `author_username`, `author_name`, `author_followers`, `author_verified`, `author_profile_picture`, `created_at`, engagement counts, & `media_urls` for each row. Keep `has_next_page` & `next_cursor` with the export checkpoint so the next run can resume the list timeline without duplicating earlier rows. Use `sinceTime` & `untilTime` for bounded backfills, and set `includeReplies=true` only when reply tweets belong in the downstream queue. Store `tweets[]` as timeline rows from accounts in the list. Use the row shape above for newsroom, monitoring, CRM, and warehouse imports. Store `has_next_page` and `next_cursor`. Only request another page when `has_next_page` is true. Direct calls use the default paid tweet page size. Treat the returned `tweets.length` as the row count returned for this page. Use `sinceTime` and `untilTime` for bounded backfills or repeat sync jobs. Leave `includeReplies` unset for a cleaner list timeline. Set `includeReplies=true` only when reply tweets belong downstream. Use `list_post_extractor` when the workflow needs a saved job with CSV/JSON/XLSX output. ## Build a Curated-List Tweet Feed Use list tweets when a list ID defines the source accounts. Keep the list ID and collection time with every tweet. Save tweet ID, text, author, creation time, engagement counts, media URLs, and cursor. Preserve the author ID because list membership can change later. Use this feed for research queues, newsroom monitoring, or account-group review. It represents tweets from curated list members, not tweets mentioning the list. Deduplicate pages by list ID and tweet ID. Persist each page before advancing its cursor. New posts can shift the first page between runs. Use list members for the curated profile roster. Use list followers for the list’s audience. Use tweet search when keywords should define the result set. ## Path Parameters List ID (numeric string). ## Query Parameters Pagination cursor from a previous response. Omit for the first page. Tweets per page. Range: `1-100`. Defaults to `20`. Unix timestamp in seconds. Only return tweets after this time. Unix timestamp in seconds. Only return tweets before this time. Include reply tweets. Default: `false`. ## Which List Endpoint? Use `GET /x/lists/{id}/tweets` for tweets from accounts in the list. Use [`GET /x/lists/{id}/members`](/api-reference/x/list-members) for accounts the list owner added to the list. Use [`GET /x/lists/{id}/followers`](/api-reference/x/list-followers) for accounts that follow the list. Use [`Create extraction`](/api-reference/extractions/create) with `list_post_extractor`, `list_member_extractor`, or `list_follower_explorer` when the workflow needs a saved export. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of tweets from the list. **Tweet object fields:** Tweet ID. Contains the complete Tweet text. Classifies the Tweet when X returns a type. ISO 8601 creation timestamp. Whether this is a Note Tweet. Omitted if unavailable. Reports the number of likes when available. Reports the number of reposts when available. Reports the number of replies when available. Reports the number of quotes when available. Reports the number of views when available. Reports the number of bookmarks when available. Permalink URL on X. Omitted if unavailable. Reports the Tweet language code when available. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. Identifies the replied-to user when available. Reports the replied-to username when available. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. Returns paid-promotion and AI-generated-media labels when available. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia`. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author X username. Author display name. Reports the author's follower count when available. Whether the author is verified. Omitted if unavailable. Profile picture URL. Omitted if unavailable. Lists media items attached to the Tweet. Omitted when none exist. **Media object fields:** Provides the direct media URL. Lists available video renditions and playback details. Omitted for images. Identifies the attached media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Cursor for the next page. Pass as the `cursor` query parameter. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "Hello from a list!", "createdAt": "2026-03-27T10:00:00.000Z", "likeCount": 42, "retweetCount": 5, "viewCount": 1200, "author": { "id": "987654321", "username": "username", "name": "Xquik", "followers": 12400, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg" } } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid list ID ```json theme={null} { "error": "invalid_list_id", "message": "List ID required" } ``` The list ID path parameter is empty. ### 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. ### 404 List not found ```json theme={null} { "error": "not_found" } ``` The list could not be resolved. Check the list ID. ### 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [List Members](/api-reference/x/list-members) · [List Followers](/api-reference/x/list-followers) # Twitter Notifications API, Mentions & Activity Feed Source: https://docs.xquik.com/api-reference/x/notifications GET /x/notifications Retrieve authenticated X account notifications, store triage rows, and route mentions, verified activity, and older pages with next_cursor. See costs. ```json theme={null} { "notifications": [ { "id": "1234567890", "type": "like", "message": "elonmusk liked your tweet", "timestamp": "2025-01-15T12:00:00Z" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* 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)
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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Requires a connected X account. Uses user-authenticated access. Get notifications reads the connected account inbox. Use `type=Mentions` for mention triage, `type=Verified` for verified-account activity, or omit `type` for all notification rows. Store `next_cursor` only when `has_next_page` is true. ```bash Mentions theme={null} curl -G https://xquik.com/api/v1/x/notifications \ --data-urlencode "type=Mentions" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq # Page 2 curl -G https://xquik.com/api/v1/x/notifications \ --data-urlencode "type=Mentions" \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} function notificationsUrl({ cursor, type = "Mentions" }) { const params = new URLSearchParams({ type }); if (cursor) params.set("cursor", cursor); return `https://xquik.com/api/v1/x/notifications?${params}`; } function toNotificationRows(page, { inboxType }) { return page.notifications.map((notification) => ({ record_type: "notification", inbox_type: inboxType, notification_id: notification.id, notification_type: notification.type ?? null, message_preview: notification.message ?? null, created_at: notification.timestamp ?? null, source_endpoint: "GET /api/v1/x/notifications", page_next_cursor: page.has_next_page ? page.next_cursor : null, })); } async function saveNotificationRows(rows) { // Replace this with a private support inbox, CRM, queue, or agent memory write. return rows.length; } const inboxType = "Mentions"; const response = await fetch(notificationsUrl({ type: inboxType }), { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const data = await response.json(); const rows = toNotificationRows(data, { inboxType }); await saveNotificationRows(rows); // Paginate if (data.has_next_page) { const next = await fetch(notificationsUrl({ type: inboxType, cursor: data.next_cursor }), { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const nextRows = toNotificationRows(await next.json(), { inboxType }); await saveNotificationRows(nextRows); } ``` ```python Python theme={null} import requests def to_notification_rows(page, inbox_type): return [ { "record_type": "notification", "inbox_type": inbox_type, "notification_id": notification["id"], "notification_type": notification.get("type"), "message_preview": notification.get("message"), "created_at": notification.get("timestamp"), "source_endpoint": "GET /api/v1/x/notifications", "page_next_cursor": page["next_cursor"] if page["has_next_page"] else None, } for notification in page["notifications"] ] def save_notification_rows(rows): # Replace this with a private support inbox, CRM, queue, or agent memory write. return len(rows) inbox_type = "Mentions" response = requests.get( "https://xquik.com/api/v1/x/notifications", params={"type": inbox_type}, headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) data = response.json() save_notification_rows(to_notification_rows(data, inbox_type)) # Paginate while data["has_next_page"]: data = requests.get( "https://xquik.com/api/v1/x/notifications", params={"type": inbox_type, "cursor": data["next_cursor"]}, headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ).json() save_notification_rows(to_notification_rows(data, inbox_type)) ``` The Node.js and Python snippets normalize each page for notification triage. Keep full message text in private systems. Use `notification_id`, `notification_type`, `created_at`, `inbox_type`, and `page_next_cursor` for private support dashboards, CRM queues, and agent workflows. ## Notification triage handoff Use `GET /api/v1/x/notifications` when a support inbox, CRM workflow, or agent queue needs account-level activity for a connected X account. The endpoint returns notification IDs, types, message previews, and timestamps. It omits full tweet and direct-message payloads. Use `type=Mentions` for replies and mentions that need a support or brand review queue. Use `type=Verified` when verified-account activity should be routed ahead of the general inbox. Omit `type` or pass `All` when the workflow needs every notification row visible to the connected account. Store `notifications[].id` as `notification_id` for dedupe and replay-safe imports. Keep `notifications[].message` in private support, CRM, or agent memory systems. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. ## Poll Twitter Notifications with the API Call `GET /x/notifications` with a connected account's Xquik API key. Omit `type` to read all notification categories. Use `type=Mentions` for a mention queue or `type=Verified` for verified-account activity. Each row can contain a notification ID, type, message, and timestamp. The route does not return full tweet, profile, or direct-message objects. Keep message text in a private support inbox, CRM, or agent queue. ### Build a Twitter API Mentions Queue Store `notifications[].id` as the stable notification key. Record the connected account ID beside every row. Upsert repeated notification IDs instead of creating duplicate support tasks. Route mention rows by `notification_type`, `message`, and `timestamp`. If an agent needs the complete public tweet, follow the related [user mentions endpoint](/api-reference/x/user-mentions). [X documents its user mentions timeline as a paginated feed of posts that mention one user.](https://docs.x.com/x-api/posts/timelines/introduction) ### Resume Notification Pages Safely Treat every response as one inbox page. Store `next_cursor` only when `has_next_page` equals `true`. Save the page's notification rows before updating the saved cursor. When the destination supports transactions, save the page and cursor together. Otherwise, upsert by notification ID. Update the cursor after the destination writes the full page. Keep the preceding cursor until validating its replacement. ### Choose Polling or Webhook Delivery This endpoint uses polling. The notification delay therefore includes the worker's polling interval. Run each new request from the latest confirmed cursor, and stop when `has_next_page` is false. Use [Xquik webhooks](/webhooks/overview) when an Xquik monitor should push captured events to your HTTPS endpoint. X also offers a separate Account Activity API for real-time account events. [Its documentation lists mentions, replies, reposts, likes, follows, and direct messages.](https://docs.x.com/x-api/account-activity/introduction) ## Twitter Notification API Questions ### Why Are Twitter API Notifications Delayed? A polling worker sees notifications only when its next request runs. Shorten the polling interval within your rate limits. Persist every cursor so later pages are not mistaken for missing notifications. ### Can I Delete or Clear Notifications with This Route? No. This route only reads notifications. Deleting a local triage row does not remove the notification from X or another connected client. ### What Happens When a Notification Request Fails? `401` means the connected account needs a valid key. `402` means the account needs more credits. Wait for `Retry-After` after `429`. Resume from the saved cursor after `424` or `502`. ## Query parameters Notification filter. `All` (default), `Verified`, or `Mentions`. Unrecognized values fall back to `All`. Pagination cursor. Pass the `next_cursor` value from the previous response to fetch the next page. ## Which inbox endpoint? Use `GET /x/notifications` for connected-account notification rows with `All`, `Verified`, or `Mentions` filters. Use [`GET /x/timeline`](/api-reference/x/timeline) for the connected account's home timeline tweets. Use [`GET /x/dm/{userId}/history`](/api-reference/x/dm-history) when the workflow needs private direct-message conversation rows. Use [`GET /x/users/{id}/mentions`](/api-reference/x/user-mentions) when you need public mention timeline rows for a user. Use [`List events`](/api-reference/events/list) after account or keyword monitors have captured replayable webhook events. Use [`Webhooks`](/webhooks/overview) when notification-like activity should push to your system instead of being polled. ## Headers Your API key. Session cookie authentication is also supported. ## Response ### 200 OK Array of notification objects. **Notification object fields:** Notification ID. Notification type (e.g. mention, like, retweet). Omitted if unavailable. Notification message text. Omitted if unavailable. ISO 8601 timestamp. Omitted if unavailable. Whether more notifications are available. Opaque cursor for the next page. Empty string when no more results. **Related:** [Timeline](/api-reference/x/timeline), [DM History](/api-reference/x/dm-history), [User Mentions](/api-reference/x/user-mentions), and [Webhooks](/webhooks/overview). # See Who Retweeted My Tweet with Twitter API Source: https://docs.xquik.com/api-reference/x/retweeters GET /x/tweets/{id}/retweeters See who retweeted a tweet with user profiles, follower counts, verification fields, cursor checkpoints & exact Twitter API examples for giveaway checks. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) Use this Twitter API to see who retweeted one public tweet. The response returns one user profile per visible reposting account. The canonical route is `GET /api/v1/x/tweets/{id}/retweeters`. ```bash First page theme={null} curl "https://xquik.com/api/v1/x/tweets/1893456789012345678/retweeters" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```bash Next page theme={null} curl -G https://xquik.com/api/v1/x/tweets/1893456789012345678/retweeters \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const tweetId = "1893456789012345678"; const response = await fetch(`https://xquik.com/api/v1/x/tweets/${tweetId}/retweeters`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const retweeterRows = data.users.map((user) => ({ source_tweet_id: tweetId, retweeter_id: user.id, username: user.username, display_name: user.name, follower_count: user.followers ?? null, following_count: user.following ?? null, verified: user.verified ?? false, verified_type: user.verifiedType ?? null, profile_image_url: user.profilePicture ?? null, })); const nextCursor = data.has_next_page ? data.next_cursor : null; const checkpoint = { source_tweet_id: tweetId, next_cursor: nextCursor }; for (const row of retweeterRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } process.stdout.write(`${JSON.stringify({ checkpoint })}\n`); ``` ```python Python theme={null} import json import requests tweet_id = "1893456789012345678" response = requests.get( f"https://xquik.com/api/v1/x/tweets/{tweet_id}/retweeters", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() next_cursor = data["next_cursor"] if data["has_next_page"] else None retweeter_rows = [ { "source_tweet_id": tweet_id, "retweeter_id": user["id"], "username": user["username"], "display_name": user["name"], "follower_count": user.get("followers"), "following_count": user.get("following"), "verified": user.get("verified", False), "verified_type": user.get("verifiedType"), "profile_image_url": user.get("profilePicture"), } for user in data["users"] ] checkpoint = {"source_tweet_id": tweet_id, "next_cursor": next_cursor} for row in retweeter_rows: print(json.dumps(row)) print(json.dumps({"checkpoint": checkpoint})) ``` The Node.js and Python snippets write JSON Lines retweeter rows plus a separate checkpoint instead of raw response pages. Persist each mapped row and the latest `next_cursor` so an import, giveaway verifier, CRM sync, or agent job can resume from the last completed page without duplicate rows. ## Direct retweeter handoff Use `GET /api/v1/x/tweets/{id}/retweeters` when a workflow needs one row per account that retweeted or reposted a tweet. Use these rows for giveaway checks, CRM imports, audience reviews, or follow-up jobs. Store `source_tweet_id`, `retweeter_id`, `username`, `display_name`, `follower_count`, `following_count`, `verified`, `verified_type`, and `profile_image_url`. Store `next_cursor` as a separate checkpoint. Store `users[]` as the profile rows for accounts that reposted the source tweet. Store `users[].id` as `retweeter_id` with `source_tweet_id` for idempotent imports and giveaway checks. Store `users[].username` and `users[].name` for handles, labels, and review queues. Store `description`, `location`, `url`, and `profilePicture` when returned for CRM and warehouse enrichment. Store `followers`, `following`, `verified`, and `verifiedType` for reach scoring, filters, and outreach priority. Use DM endpoints only after a user-approved message flow. Treat the write response as the delivery authority. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. Use `users.length`, not a requested page size, for row counts. Low balances can return fewer rows. Direct retweeter reads cost 1 credit per user returned. Low credit balances can return fewer users than a full page; zero affordable results return `402 insufficient_credits`. ## Retweeter Questions ### Can I See Who Retweeted My Tweet? Yes, for profiles visible to this endpoint. Pass the tweet's numeric ID. The response lists handles, names, follower counts, and verification fields. Continue pagination only when the response confirms another page. [X documents this read intent as Get Reposted by.](https://docs.x.com/x-api/posts/get-reposted-by) ### Why Can't I See Every Retweeter? X controls which profiles each request exposes. Deleted, protected, blocked, or withheld accounts may not appear. X may also omit accounts it cannot return. Pagination and credit limits can shorten one page. A missing profile cannot confirm zero repost activity. ### Do Retweeters Include Quote Tweets? No. A standard repost shares the source tweet without added commentary. [The quote-tweets endpoint returns added commentary.](/api-reference/x/tweet-quotes) [Use the tweet-replies endpoint to read the conversation.](/api-reference/x/tweet-replies) ### How Do I Export Retweeters? The Node.js and Python examples write one JSON Lines row per user. Store the source tweet ID beside each profile. Use `next_cursor` to resume an interrupted export. Use `toolType=repost_extractor` for a saved CSV, JSON, or XLSX job. ### How Can I Analyze Retweeters? Compare complete snapshots with numeric user IDs. Record the collection time beside follower and verification fields. A repost does not prove endorsement or a business relationship. Report only returned profile attributes. ## Path parameters Tweet ID (numeric string). ## Query parameters Pagination cursor from `next_cursor` in a previous response. Omit it on the initial request. Pass it only when `has_next_page` is true. Profiles per page. Range: `20-200`. Defaults to `200`. ## Which tweet engagement endpoint? Use `GET /x/tweets/{id}/retweeters` for user profiles that reposted one source tweet. Use [`GET /x/tweets/{id}/favoriters`](/api-reference/x/favoriters) for user profiles that liked one source tweet. Use [`GET /x/tweets/{id}/quotes`](/api-reference/x/tweet-quotes) when you need tweet rows that quote the source tweet. Use [`GET /x/tweets/{id}/replies`](/api-reference/x/tweet-replies) when you need reply tweet rows under the source tweet. Use [`Create extraction`](/api-reference/extractions/create) with `toolType=repost_extractor` when you need a saved job or CSV, JSON, or XLSX export. Use [`Send DM`](/api-reference/x-write/send-dm) only after your workflow has a user-approved outreach step. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of users who retweeted. **User object fields:** X user ID. X username. Display name. Profile bio. This value records the follower count. Following count. Verified status. Profile image URL. Profile location. Account creation date (ISO 8601). This value records the total tweet count. X may omit it. This value contains the cover image URL. X may omit it. This value records the media tweet count. X may omit it. Website URL from profile. Omitted if empty. This value records the liked tweet count. X may omit it. X sets this flag for accounts with custom timelines. X may omit it. X sets this flag for translator accounts. X may omit it. Country codes where the account is withheld. Omitted if empty. X sets this flag for sensitive accounts. X may omit it. This array contains pinned tweet IDs. Omitted if none. X sets this flag for automated accounts. X may omit it. Username of the account operator if automated. Omitted if not automated. X sets this flag when it cannot return the account. X may give a reason for a missing account. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Use this object for bio text and linked profile entities. X may omit it. X sets this flag for X Premium verification. X may omit it. Use this field for normalized verification status. X may omit it. This value contains the profile banner URL. X may omit it. X sets this flag for protected accounts. X may omit it. Role within the requested community context. Omitted outside community results. Whether more results are available. Cursor for the next page. ### 401 Unauthenticated Anonymous requests receive `WWW-Authenticate: Bearer`. This is not a Payment challenge. ### 402 Payment required Account keys receive account options. A guest wallet receives a checkout option. Confirm any payment action. **Related:** [Tweet favoriters](/api-reference/x/favoriters) · [Quote tweets](/api-reference/x/tweet-quotes) · [Tweet replies](/api-reference/x/tweet-replies) # Twitter Advanced Search API & Tweet Scraper Source: https://docs.xquik.com/api-reference/x/search-tweets GET /x/tweets/search Search tweets by keyword, ID, or URL. Return text, authors, replies, metrics, media, and cursors for CRM, agents, or exports. Includes request fields. ```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" } ``` ```json theme={null} { "error": "missing_query" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "Maximum coverage is busy. Retry shortly." } ```
For the complete documentation index, see llms.txt.
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. Large `limit` pulls are resumable: if `has_next_page` is `true`, pass `next_cursor` back as `cursor` with the same query, filters, `queryType`, and `limit`. If zero paid results are affordable, it returns `402 insufficient_credits`. Use Search Tweets as an advanced Twitter search API for 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. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit 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. 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`. 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. Finished, expired, superseded, or identity-mismatched cursors return `410 coverage_cursor_gone`. The response omits `Retry-After`. Restart without a cursor. Deduplicate restarted results by `id`. Malformed cursors return `400 invalid_coverage_cursor`. Restart without them. Fresh searches return available automatic rows when some views fail. If automatic coverage cannot start, Xquik uses standard pagination. Coverage cursors never switch sources mid-sequence. ```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)); } ``` ## Direct API handoff Use `GET /x/tweets/search` when an app, queue worker, CRM enrichment job, or agent needs the latest matching tweets without creating a stored extraction job. It returns paginated JSON for live search pages and app ingestion. The examples above write JSON Lines rows with tweet fields, author ID, username, display name, follower count, verified state, profile image URL, media, and cursor fields so a worker can resume from the last saved `next_cursor`. Use [`tweet_search_extractor`](/guides/tweet-scraper-csv-export) instead when a team needs an estimate, extraction ID, saved result pages, or CSV, JSON, and XLSX downloads after completion. Call `GET /x/tweets/search` with `q`, filters, `queryType`, `limit`, and `cursor` for low-latency JSON rows. Send a plain Tweet ID or X status URL in `q` when the source queue stores links. Run `tweet_search_extractor` for estimates, job status, stored pages, and downloadable files. For explicit `limit` pulls, treat `limit` as a batch-size upper bound. If the response returns fewer tweets than `limit` and `has_next_page` is `true`, store `next_cursor` and continue with the same `q`, structured filters, `queryType`, and `limit` plus `cursor`. De-duplicate stable IDs and reject repeated cursors. Return a partial-result checkpoint if pagination stalls. 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. `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. 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. For exact lookups, a plain Tweet ID or X status URL in `q` returns that tweet when available. Send no cursor on the first lookup; cursor requests return an empty final page for exact IDs. For CSV or XLSX output, project the returned `tweets[]` rows locally or use [`tweet_search_extractor`](/guides/tweet-scraper-csv-export) for saved CSV, JSON, or XLSX files. ## Advanced Twitter search patterns | Search intent | Request pattern | | --------------------------- | ---------------------------------------------------- | | Twitter keyword search | `q=product%20launch` | | 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` | Store `tweets[]` as the matching tweet rows for app ingestion, analyst export, or retrieval. Store `tweets[].id` as the stable tweet key for dedupe, CRM notes, queues, and follow-up lookups. Store `tweets[].text` and `tweets[].createdAt` for search hit context and time ordering. 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. Store engagement counts for scoring, routing, and prioritization. Store `tweets[].media`, `quoted_tweet`, and `retweeted_tweet` to preserve attached media and relationship context when available. 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. Use `tweet_search_extractor` when the output must be saved CSV, JSON, or XLSX. Tweet search costs 1 credit per tweet returned. Low credit balances can return fewer tweets than `limit`; zero affordable results return `402 insufficient_credits`. Retry `429` with the `Retry-After` header, and retry `424` or `502` after a short backoff. ## Query parameters Search query. Supports X search operators such as `from:username`, `to:username`, `#hashtag`, and boolean operators. A plain Tweet ID or X status URL returns the exact tweet when available. Sort order for search results. `Top` returns most relevant tweets, `Latest` returns most recent. Defaults to `Latest`. 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. Pass `next_cursor` back unchanged. New Xquik cursors resume automatic coverage. Existing unprefixed cursors keep legacy behavior. Inclusive lower bound. Intersects with inline bounds. Exclusive upper bound. Intersects with inline bounds. Maximum Tweets requested per automatic page. Use `1` through `10000`. A page can return fewer. Keep the same limit when continuing with `cursor`. ### Structured filters Structured filters are part of the public Search Tweets API. Xquik converts them into X search operators before fetching results, then filters returned rows again when the response fields are available. Keep the same filters on every cursor request. Filter to tweets authored by this username. The `@` prefix is optional. Filter to replies directed to this username. Filter to tweets that mention this username. Only include tweets with this language code, such as `en`, `tr`, or `es`. Filter to tweets created on or after this date or timestamp. Filter to tweets created before this date or timestamp. A `YYYY-MM-DD` value includes the whole day before the boundary. Filter by attached media or links. Values: `images`, `videos`, `gifs`, `media`, `links`, `none`. Only include tweets meeting this minimum like count. Only include tweets meeting this minimum repost count. Minimum reply count. Minimum quote count. Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Tweets without a count pass this filter. Maximum Tweet repost count. Tweets without a count pass this filter. Maximum Tweet reply count. Tweets without a count pass this filter. Maximum Tweet quote count. Tweets without a count pass this filter. When `true`, only return Tweets from Blue-verified authors. Match the Tweet card name. Match the source application. Exclude Tweets from this source application. Match latitude, longitude, and radius in X search syntax. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match this place name. Set the radius for the `near` filter. Match Tweets inside this recent time window. When `true`, only return native reposts. When `true`, enable X safe-search filtering. When `true`, only return news results. When `true`, only return tweets from verified authors. Set `include`, `exclude`, or `only` for reply tweets. Set `include`, `exclude`, or `only` for reposts. Set `include`, `exclude`, or `only` for quote tweets. Exact text that must appear in the tweet. Words or quoted phrases to exclude from returned tweets. Use commas or whitespace between values. Words or quoted phrases where at least 1 term must appear. Use commas or whitespace between values. Only include tweets matching these hashtags. Use commas or whitespace between values. The `#` prefix is optional. Only include tweets matching these cashtags. Use commas or whitespace between values. The `$` prefix is optional. URL substring or domain that must appear in tweet URL entities. Filter to tweets in this conversation thread. Only include replies to this tweet ID. Filter to quote tweets of this tweet ID. Filter to retweets of this tweet ID. ### 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. Search within this X List ID. Search within this X place ID. Search within this country code. Geo point radius in X search syntax, such as `-73.99 40.73 25mi`. Geo bounding box in X search syntax, such as `-74.1 40.6 -73.9 40.8`. Raw X search operators appended to the final search query. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of matching tweets. **Tweet object fields:** Fields absent from a source tweet are omitted. Tweet ID. Tweet text. Tweet type. ISO 8601 creation time. Whether this is a Note Tweet. Like count. Repost count. Reply count. Quote count. View count. Bookmark count. Tweet URL. Tweet language code. Whether the tweet is a reply. Tweet ID being replied to. Omitted if not a reply. User ID being replied to. Omitted if not a reply. Username being replied to. Omitted if not a reply. Conversation thread ID. Tweet client. Rendered text offsets. Whether replies are limited. Whether this tweet quotes another. Parsed entities. Disclosure labels. Tweet author profile. **Author object fields:** Author user ID. Author X username. Author display name. Follower count. Following count. Whether the author is verified. Profile image URL. Cover image URL. Profile bio. Profile location. Account creation date. Total tweet count. Attached media items. Omitted when the tweet has no attached media. **Media item fields:** Direct media URL (pbs.twimg.com). Video variants. Omit for images. Media type: `photo`, `video`, or `animated_gif`. Shortened t.co URL from the tweet text. Embedded quoted tweet (same shape as tweet object). Omitted if not a quote tweet. Original retweeted tweet (same shape as tweet object). Omitted if not a retweet. Whether more results are available. Pass `next_cursor` to fetch the next page. Opaque cursor for the next page. Empty string when no more results. ```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 ```json theme={null} { "error": "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. ### 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 } ``` Your tier rate limit was exceeded. 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. **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. # Twitter User Search, Lookup & Audience Counts Source: https://docs.xquik.com/api-reference/x/search-users GET /x/users/search Search Twitter or X users by name or username. Return matching profiles, biographies, follower counts, verification status, and pagination. See costs. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit ```bash cURL theme={null} curl "https://xquik.com/api/v1/x/users/search?q=xquik" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const query = "xquik"; const params = new URLSearchParams({ q: query }); const response = await fetch(`https://xquik.com/api/v1/x/users/search?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const nextCursor = data.has_next_page ? data.next_cursor : null; const searchRows = data.users.map((user, index) => ({ search_query: query, result_rank: index + 1, user_id: user.id, username: user.username, display_name: user.name, bio: user.description ?? null, follower_count: user.followers ?? null, following_count: user.following ?? null, verified: user.verified ?? false, profile_image_url: user.profilePicture ?? null, next_cursor: nextCursor, })); ``` ```python Python theme={null} import requests query = "xquik" response = requests.get( "https://xquik.com/api/v1/x/users/search", params={"q": query}, headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() next_cursor = data["next_cursor"] if data["has_next_page"] else None search_rows = [ { "search_query": query, "result_rank": index + 1, "user_id": user["id"], "username": user["username"], "display_name": user["name"], "bio": user.get("description"), "follower_count": user.get("followers"), "following_count": user.get("following"), "verified": user.get("verified", False), "profile_image_url": user.get("profilePicture"), "next_cursor": next_cursor, } for index, user in enumerate(data["users"]) ] ``` The Node.js and Python snippets shape durable search-result rows instead of printing full profile pages. Persist `searchRows` or `search_rows` with `nextCursor` or `next_cursor` before requesting the next page. ## Direct user search handoff Use `GET /x/users/search` when a CRM, enrichment, creator discovery, support, or agent workflow has a name, brand, or handle fragment and needs matching X profiles. Use [Get User](/api-reference/x/twitter-profile-lookup) when you have one exact ID or username. Use [Get Users (Batch)](/api-reference/x/batch-users) when you already have numeric user IDs. Store `search_query`, `result_rank`, `user_id`, `username`, `display_name`, profile metrics, verification state, `profile_image_url`, `has_next_page`, and `next_cursor`. Treat `next_cursor` as opaque and pass it back as `cursor` only when `has_next_page` is true. Zero affordable results return `402 insufficient_credits`. ## Resolve the intended X profile Use user search when a workflow starts with a name or username fragment. Inspect several candidates before choosing a numeric user ID. Similar profile names can represent unrelated people or organizations. Show reviewers: * Username and profile name. * Biography and location. * Verification state. * Follower and following counts. * Profile image and numeric user ID. Store the chosen user ID for later requests. Usernames can change. Numeric IDs remain the safer handoff for followers, following, timelines, and profile lookups. Search results can be empty without representing an API failure. Separate no matches from invalid authentication, rate limits, and insufficient credits. Do not use a result row number as identity. Ranking can change between searches. Always pass the selected user ID into the next workflow. ## Query parameters Search query string. Pagination cursor from a previous response. Omit for the first page. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of matching user profiles. **User object fields:** X user ID. X username. Display name. Profile bio. Follower count. Following count. Verified status. Profile image URL. Profile location. Account creation date (ISO 8601). Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines. Omitted if unavailable. Whether the user is an X translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. IDs of pinned tweets. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable. Omitted if available. Reason the account is unavailable. Omitted if available. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. Role within the requested community context. Omitted outside community results. Whether more results are available. Cursor for the next page. ```json theme={null} { "users": [ { "id": "987654321", "username": "username", "name": "Xquik", "followers": 10000, "verified": true, "description": "All-in-one X automation platform" } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Missing query ```json theme={null} { "error": "missing_query" } ``` ### 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 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 } ``` Your tier rate limit was exceeded. 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. **Next steps:** [Get User](/api-reference/x/twitter-profile-lookup) to fetch full details, or [Search Tweets](/api-reference/x/search-tweets) to find tweets by query. # Twitter Timeline API for Home Feed Tweets & Media Source: https://docs.xquik.com/api-reference/x/timeline GET /x/timeline Use the Twitter timeline API to get a connected account's home feed. Paginate tweets, authors, replies, reposts, likes, views, media, and cursors safely. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* 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)
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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Requires a connected X account. Uses user-authenticated access. ```bash cURL theme={null} curl https://xquik.com/api/v1/x/timeline \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq # Page 2 curl -G https://xquik.com/api/v1/x/timeline \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const baseUrl = "https://xquik.com/api/v1/x/timeline"; const seenTweetIds = new Set(); let pageCursor = ""; for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { const params = new URLSearchParams(); if (pageCursor !== "") params.set("cursor", pageCursor); if (seenTweetIds.size > 0) { params.set("seenTweetIds", Array.from(seenTweetIds).join(",")); } const query = params.toString(); const response = await fetch(query === "" ? baseUrl : `${baseUrl}?${query}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" }, }); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const timelineRows = page.tweets.map((tweet) => { seenTweetIds.add(tweet.id); return { timeline_source: "home", tweet_id: tweet.id, tweet_url: tweet.url ?? null, 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; pageCursor = page.next_cursor; } ``` ```python Python theme={null} import json import requests seen_tweet_ids = set() page_cursor = "" for page_index in range(3): params = {} if page_cursor: params["cursor"] = page_cursor if seen_tweet_ids: params["seenTweetIds"] = ",".join(sorted(seen_tweet_ids)) response = requests.get( "https://xquik.com/api/v1/x/timeline", params=params, headers={"x-api-key": "xq_YOUR_KEY_HERE"}, ) page = response.json() if not response.ok: raise RuntimeError(page) for tweet in page["tweets"]: seen_tweet_ids.add(tweet["id"]) timeline_row = { "timeline_source": "home", "tweet_id": tweet["id"], "tweet_url": tweet.get("url"), "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 page_cursor = page["next_cursor"] ``` ## Home timeline handoff Use `GET /x/timeline` for the authenticated account's home feed. The examples write JSON Lines rows. They include author ID, username, display name, and tweet text. Rows include follower count, verified state, profile image URL, `seenTweetIds`, and cursor fields. Store processed tweet IDs. Then pass them as `seenTweetIds` with the last saved `next_cursor`. This home timeline Twitter API workflow supports inboxes and CRM routing. A Twitter API timeline also supports approved monitoring. Store the connected account ID and collection time with each page. Preserve tweet IDs, authors, text, engagement counts, replies, media, and cursors. X controls ranking and source availability. Do not treat the response as a complete public archive. X explains the feed model in its [home and user timeline documentation](https://docs.x.com/x-api/posts/timelines/introduction). Store one row per `tweets[]` item with `timeline_source: "home"` for the connected account. Add processed tweet IDs to `seenTweetIds` before requesting the next page. Store `has_next_page` and `next_cursor`. Pass `next_cursor` back as `cursor` only when `has_next_page` is true. Keep home timeline rows in account-scoped inbox, CRM, monitor seed, or agent memory systems. | Home timeline column | Response source | Feed use | | -------------------- | ---------------------- | -------------------------------------------------- | | `tweet_id` | `tweets[].id` | Deduplicate rows and build tweet URLs. | | `author_id` | `tweets[].author.id` | Keep stable author identity when usernames change. | | `page_cursor` | Request `cursor` | Trace the page that produced each row. | | `next_cursor` | Response `next_cursor` | Resume after storing the current page. | ## Twitter API Timeline Pagination Use Twitter API timeline pagination to process each home-feed page. Use each returned tweet ID to deduplicate timeline tweets. Save each page before advancing its cursor. Keep the prior cursor until validating its replacement. Send the last `next_cursor` as `cursor` after storing the full page. Pass processed tweet IDs through `seenTweetIds` to reduce repeat rows. Stop when `has_next_page` is false or `next_cursor` is empty. Respect the `Retry-After` header after a 429 response. After a 424 or 502 response, retry the stored cursor. Never advance a checkpoint after a failed destination write. ## Route Home Timeline Tweets | Routing rule | Concrete match | Destination | | ---------------- | ------------------------------------------ | --------------------------------------- | | Support reply | Approved account or keyword | Support review queue | | Campaign mention | Campaign phrase in `text` | Campaign verification queue | | Media review | Non-empty `media` | Image or video review | | High engagement | Approved likes, replies, reposts, or views | Priority research queue | | No rule match | No approved condition matches | Store without generating a notification | Keep each rule name beside its tweet ID. Preserve the original tweet text. Reprocess a tweet only after its routing rule changes. ## Twitter Timeline API Questions ### How Do You Authenticate Timeline Requests? Send an Xquik API key through the `x-api-key` header. Keep keys server-side. Never expose a key in a browser, mobile bundle, or public repository. ### Can You Filter Home Timeline Tweets by Hashtag? No. `GET /x/timeline` returns the connected account's ranked home feed. Use [tweet search](/api-reference/x/search-tweets) for keyword, author, date, or media filters. ### Can You Display Timeline Tweets in an App? Yes. Render `tweets[]` with the returned text, author, media, and tweet URL. Store tweet IDs for deduplication. Refresh from the last confirmed cursor. ### How Should Timeline API Errors Be Retried? Fix authentication after a 401 response. Add credits after a 402 response. Respect `Retry-After` after 429. Resume from the stored cursor after 424 or 502. ## Query parameters Pagination cursor. Pass the `next_cursor` value from the previous response to fetch the next page. Comma-separated tweet IDs to exclude from results. Ignore empty entries. Use this to avoid returning tweets the user has already seen. ## Which timeline endpoint? Use `GET /x/timeline` for the connected account's home feed. Use [`GET /x/users/{id}/tweets`](/api-reference/x/user-tweets) for one public profile's timeline. Use [`GET /x/users/{id}/mentions`](/api-reference/x/user-mentions) for public mentions of one account. Use [`GET /x/bookmarks`](/api-reference/x/bookmarks) for tweets the connected account saved. Use [`GET /x/notifications`](/api-reference/x/notifications) for compact inbox activity rows. Use [`List events`](/api-reference/events/list) after account or keyword monitors have captured replayable webhook events. ## Headers Your API key. You can also authenticate with an OAuth bearer token. ## Response ### 200 OK Array of timeline tweets. **Tweet object fields:** Tweet ID. Contains the tweet text. Returns the tweet type when available. Returns an ISO 8601 timestamp when available. Marks long-form Note Tweets when available. Counts likes when available. Counts reposts when available. Counts replies when available. Counts quote tweets when available. Counts views when available. Counts bookmarks when available. Links to the tweet on X when available. Identifies the tweet language when available. Marks replies when available. Identifies the parent tweet for a reply. Identifies the replied-to user. Identifies the replied-to username. Identifies the conversation when available. Identifies the posting client when available. Provides rendered text offsets when available. Shows whether X limits replies. Marks quote tweets when available. Returns parsed entities when available. Describes paid partnerships and AI-generated media. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Returns the tweet author when available. **Author object fields:** Identifies the author. Returns the current X username. Returns the display name. Counts the author's followers. Shows whether X marks the author as verified. Returns the profile image URL when available. Lists attached images, GIFs, or videos when available. **Media item fields:** Returns the direct media URL. Lists video renditions with bitrates, content types, and URLs. Identifies the media type. Returns the shortened URL from the tweet text. Embeds the quoted tweet when present. Embeds the original repost when present. Shows whether more tweets remain. Provides the next page cursor. Empty after the final page. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "Timeline tweet content", "createdAt": "2026-02-24T10:00:00.000Z", "likeCount": 200, "retweetCount": 50, "replyCount": 15, "url": "https://x.com/user/status/1893456789012345678", "author": { "id": "44196397", "username": "elonmusk", "name": "Elon Musk", "followers": 150000000, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg" } } ], "has_next_page": true, "next_cursor": "DAADDAABCgABF..." } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 402 Insufficient credits ```json theme={null} { "error": "insufficient_credits" } ``` Metered access requires enough available credits. Possible error values include `no_subscription`, `subscription_inactive`, `no_credits`, and `insufficient_credits`. ### 429 Rate Limit Exceeded ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` The API key, user, or plan tier is sending requests too quickly. Respect the `Retry-After` header before retrying. ### 502 X API unavailable ```json theme={null} { "error": "x_api_unavailable" } ``` The read service failed. Retry after a short delay. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` The opt-in normalized v1 contract returns `424` when the read service fails. Send `xquik-api-contract: 2026-04-29` to opt in. Default v1 returns `502`. **Related:** [Notifications](/api-reference/x/notifications) · [Bookmarks](/api-reference/x/bookmarks) # Twitter Trends API by Region, WOEID & Search Source: https://docs.xquik.com/api-reference/x/trends GET /x/trends Get Twitter and X trends by WOEID region. Return ranked topics, hashtags, search queries, descriptions, tweet volume, and result counts. See examples. ```json theme={null} { "trends": [ { "name": "#AI", "description": "Artificial intelligence discussions", "promotedContent": null, "query": "%23AI", "rank": 1 } ], "count": 30, "woeid": 1 } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
**3 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Direct [MPP](/mpp/machine-payments-protocol): USD 0.00045 per call ```bash cURL theme={null} curl -G https://xquik.com/api/v1/x/trends \ --data-urlencode "woeid=23424977" \ --data-urlencode "count=10" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const params = new URLSearchParams({ woeid: "23424977", count: "10" }); const response = await fetch(`https://xquik.com/api/v1/x/trends?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const detectedAt = new Date().toISOString(); const trendRows = data.trends.map((trend) => ({ trend_name: trend.name, rank: trend.rank ?? null, description: trend.description ?? null, search_query: trend.query ?? trend.name, region_woeid: data.woeid, returned_count: data.count, detected_at: detectedAt, })); for (const row of trendRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } ``` ```python Python theme={null} from datetime import datetime, timezone import json import requests response = requests.get( "https://xquik.com/api/v1/x/trends", params={"woeid": 23424977, "count": 10}, headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() detected_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") trend_rows = [ { "trend_name": trend["name"], "rank": trend.get("rank"), "description": trend.get("description"), "search_query": trend.get("query", trend["name"]), "region_woeid": data["woeid"], "returned_count": data["count"], "detected_at": detected_at, } for trend in data["trends"] ] for row in trend_rows: print(json.dumps(row)) ``` Use `GET /x/trends` to retrieve ranked regional topics. This Twitter trends API supports planning, monitor seeding, search jobs, and alerts. Each example emits one JSON record for every returned trend. Store `trend_name` for every result. Preserve `rank`, `description`, and `query` when the response includes them. Leave omitted fields unset. Use the trend name when `query` is missing. Also store `region_woeid`, `returned_count`, and your own `detected_at` timestamp. Pass `search_query` to [Search Tweets](/api-reference/x/search-tweets) for matching tweets. A Twitter API trending workflow should preserve each snapshot before searching. ## Build a Regional Trend Monitor Poll one WOEID on a consistent schedule. Store every trend name and returned optional field. Keep missing values unset. Preserve `tweetVolume` as null when X returns null. Compare ranks within the same region. Do not mix worldwide and country results inside one ranking. Keep the WOEID on every row. Use the returned search query to retrieve matching tweets. Preserve the trend snapshot before starting that search. This links each tweet batch to the regional topic that triggered it. Treat missing tweet volume as unknown. Do not convert it to zero. X may omit that field for some trends. ## Query parameters Positive Where On Earth ID for the region. Invalid or nonpositive values fall back to `1` (worldwide). See [Trends guide](/guides/trends) for common regions. Number of trends to return. Default `30`, max `50`. Invalid or below-minimum values fall back to `30`. ## Headers Send a full Xquik account API key. `Bearer xq_your_guest_key_here` authenticates `paid_reads` guest keys through the API-key scheme's Bearer alias. Direct MPP uses the `Payment ...` credential from the `WWW-Authenticate: Payment` challenge. ## Response ### 200 OK Array of trending topics. **Trend object fields:** Trend name or hashtag. Trend rank. Omitted if unavailable. Trend description. Omitted if unavailable. Search query for this trend. Omitted if unavailable. Promotion identifier. Null for organic trends. Approximate public post volume when X supplies it. X search URL for the trend. Number of trends returned. WOEID used for the request. ```json theme={null} { "trends": [ { "name": "#AI", "rank": 1, "description": "Trending in Technology", "query": "%23AI", "promotedContent": null, "tweetVolume": 250000, "url": "https://x.com/search?q=%23AI" } ], "count": 1, "woeid": 23424977 } ``` ### 401 Unauthenticated ```json theme={null} { "error": "unauthenticated" } ``` Missing or invalid API key. ### 402 Payment required Account keys receive account payment options. Guest keys receive only the guest top-up action. Anonymous calls receive `WWW-Authenticate: Payment` plus a guest wallet action. Failed requests never create checkout. Confirm a payment option first. ### 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 } ``` The Xquik tier limit blocked the request. Use `Retry-After` when present. Otherwise, use the JSON `retryAfter` field. ### 424 Dependency Failed ```json theme={null} { "error": "x_api_unavailable" } ``` The opt-in normalized contract returns 424 when the read service fails. Send `xquik-api-contract: 2026-04-29` to opt in. Default v1 returns 502. ## Twitter Trends API Questions ### How Do I Get Twitter Trends Programmatically? Call `GET /x/trends` with a regional `woeid` and a result `count`. Read each trend's name, rank, query, description, and available tweet volume. ### Can I Track Trending Hashtags by Region? Yes. Poll the same WOEID on a stable schedule. Compare ranks within the same region. Save each detection time. Use `query` to search tweets related to each trending hashtag. Use the trend name when the response omits `query`. ### How Do I Authenticate to the Twitter Trends API? Send an Xquik API key or OAuth bearer token. A `paid_reads` guest key can use the API-key scheme's Bearer alias. For direct MPP, anonymous requests get a payment challenge. ### How Much Does a Trends Request Cost? The pricing callout above shows the current credit and direct MPP cost. Failed requests return a documented error without creating checkout. ### How Do I Build Trend Tracking Into an App? Schedule one regional request, then store each returned snapshot. Compare the latest ranks with the previous snapshot. Send meaningful changes to a dashboard, monitor, or alert queue. ### Does This Replace X's Official API? No. This page documents Xquik, an independent third-party service. It does not document X's official API. This endpoint returns trends directly from X. For broader Radar topic discovery, use [List Radar Items](/api-reference/radar/list). **Next steps:** [Search Tweets](/api-reference/x/search-tweets) to find tweets about a trending topic. # View Quote Tweets With Twitter API & Author Fields Source: https://docs.xquik.com/api-reference/x/tweet-quotes GET /x/tweets/{id}/quotes View quote tweets for an original tweet with a Twitter API, author profiles, commentary, media, engagement counts, filters, cursors, alerts & error responses. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) Use this Twitter API to view quote tweets for one original tweet. Each row includes the quoting tweet's commentary, author, engagement counts, and media attachments. The canonical route remains `GET /api/v1/x/tweets/{id}/quotes`. ```bash First page theme={null} curl "https://xquik.com/api/v1/x/tweets/1893456789012345678/quotes" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```bash Next page with filters theme={null} curl -G https://xquik.com/api/v1/x/tweets/1893456789012345678/quotes \ --data-urlencode "cursor=abc123" \ --data-urlencode "sinceTime=1774500000" \ --data-urlencode "includeReplies=false" \ --data-urlencode "verifiedOnly=true" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const tweetId = "1893456789012345678"; const params = new URLSearchParams({ sinceTime: "1774500000", includeReplies: "false", }); const response = await fetch(`https://xquik.com/api/v1/x/tweets/${tweetId}/quotes?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const nextCursor = data.has_next_page ? data.next_cursor : null; const quoteRows = data.tweets.map((tweet) => ({ quoted_tweet_id: tweetId, quote_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, media_urls: tweet.media?.map((item) => item.mediaUrl).filter(Boolean) ?? [], })); const checkpoint = { quoted_tweet_id: tweetId, next_cursor: nextCursor }; for (const row of quoteRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } process.stdout.write(`${JSON.stringify({ checkpoint })}\n`); ``` ```python Python theme={null} import json import requests tweet_id = "1893456789012345678" response = requests.get( f"https://xquik.com/api/v1/x/tweets/{tweet_id}/quotes", params={"sinceTime": "1774500000", "includeReplies": "false"}, headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() next_cursor = data["next_cursor"] if data["has_next_page"] else None quote_rows = [ { "quoted_tweet_id": tweet_id, "quote_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"), "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") ], } for tweet in data["tweets"] ] checkpoint = {"quoted_tweet_id": tweet_id, "next_cursor": next_cursor} for row in quote_rows: print(json.dumps(row)) print(json.dumps({"checkpoint": checkpoint})) ``` The Node.js and Python snippets write JSON Lines quote rows plus a separate checkpoint instead of raw response pages. Persist each mapped row and the latest `next_cursor` so a moderation queue, campaign report, research job, or agent handoff can resume from the last completed page without duplicate rows. ## Direct quote tweet handoff Use `GET /api/v1/x/tweets/{id}/quotes` when a support, campaign, moderation, research, or agent workflow needs quote tweets as JSON rows. It returns one row for every quote tweet in the response. Store `quoted_tweet_id`, `quote_id`, `text`, `author_id`, `author_username`, `author_name`, `author_followers`, `author_verified`, `author_profile_picture`, `created_at`, engagement counts, media URLs, and a separate `next_cursor` checkpoint. Use `sinceTime`, `untilTime`, `includeReplies`, and tweet result filters to bound the quote set before exporting rows downstream. Store `tweets[]` as quote tweet rows for one source tweet. Store `tweets[].id` as `quote_id` with `quoted_tweet_id` for idempotent imports and moderation queues. Store `tweets[].author.id`, `username`, `name`, `followers`, `verified`, and `profilePicture` for CRM, research, and review tools. Store `likeCount`, `replyCount`, `retweetCount`, `quoteCount`, `viewCount`, and `bookmarkCount` when returned. Store `media[].mediaUrl`, `entities`, `quoted_tweet`, and `retweeted_tweet` when returned to preserve attached context. Use `sinceTime`, `untilTime`, `includeReplies`, and tweet result filters to narrow campaign, support, or audit windows. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. Use `tweets.length`, not a requested page size, for row counts. Low balances can return fewer rows. Direct quote tweet reads cost 1 credit per tweet returned. Low credit balances can return fewer tweets than a full page; zero affordable results return `402 insufficient_credits`. ## Historical pages vs live quote alerts Use this endpoint when you need existing quote tweets for one source tweet. Use monitors when future quote activity should arrive as stored events or signed webhook deliveries. Call `GET /x/tweets/{id}/quotes`, store `quote_id`, and resume with `next_cursor` for one known source tweet. Use [`POST /monitors`](/api-reference/monitors/create) with `eventTypes: ["tweet.quote"]` when one tracked account's future quote tweets should produce events. Use [`POST /monitors/keywords`](/api-reference/monitors/create-keyword) with `eventTypes: ["tweet.quote"]` when matching future quote tweets should produce events. Use [`POST /webhooks`](/api-reference/webhooks/create) with `tweet.quote`, verify signatures, and replay stored rows with [`GET /events`](/api-reference/events/list). ## Quote Tweet Questions ### What Are Quote Tweets? A quote tweet is a new tweet containing commentary about an original tweet. X calls this format a [Quote Post](https://docs.x.com/x-api/posts/quote-tweets/introduction). This endpoint returns the quoting tweet, its author, and visible engagement. It never changes the original tweet. ### How Does a Twitter API View Quote Tweets? Pass the original tweet's numeric ID through the path. Store every returned `quote_id` before requesting `next_cursor`. Keep the original tweet ID beside each row for attribution, deduplication, and campaign reporting. ### Why Are Some Quote Tweets Not Showing? X controls which quote tweets each request exposes. Deleted, protected, withheld, blocked, or unavailable tweets may not appear. Filters can also remove otherwise visible rows. A missing row cannot confirm zero quote activity. ### How Do Quote Tweets Differ From Replies and Reposts? A quote tweet adds its author's commentary and references the original tweet. A repost shares the original without added commentary. The replies endpoint returns tweets from the original conversation. Use retweeters for reposting profiles. ### How Can Teams Analyze Quote Tweets? Store text, author, creation time, engagement counts, and media URLs. Keep the source relationship separate. Use both tweet IDs as the deduplication key. Use monitors and signed webhooks for future quote alerts. ## Path parameters Tweet ID (numeric string). ## Query parameters Pagination cursor from `next_cursor` in a previous response. Leave it empty for the initial request. Pass a cursor only when `has_next_page` is true. Tweets per page. Range: `1-100`. Defaults to `20`. Unix timestamp in seconds. Only return quotes after this time. Unix timestamp in seconds. Only return quotes before this time. Include reply tweets. Default: `false`. ### Tweet result filters These optional filters apply to `tweets[]` returned by this route. They keep the same quoted tweet and filter rows after each page is fetched, so selective filters can return fewer rows than an unfiltered page. Filter to tweets authored by this username. The `@` prefix is optional. Filter to replies directed to this username. Filter to tweets that mention this username. Only include tweets with this language code, such as `en`, `tr`, or `es`. Filter to tweets created on or after this date or timestamp. Filter to tweets created before this date or timestamp. A `YYYY-MM-DD` value includes the whole day before the boundary. Filter by attached media or links. Values: `images`, `videos`, `gifs`, `media`, `links`, `none`. Only include tweets meeting this minimum like count. Only include tweets meeting this minimum repost count. Minimum reply count. Minimum quote count. Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Tweets without a count pass this filter. Maximum Tweet repost count. Tweets without a count pass this filter. Maximum Tweet reply count. Tweets without a count pass this filter. Maximum Tweet quote count. Tweets without a count pass this filter. When `true`, only return Tweets from Blue-verified authors. Match the Tweet card name. Match the source application. Exclude Tweets from this source application. Match latitude, longitude, and radius in X search syntax. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match this place name. Set the radius for the `near` filter. Match Tweets inside this recent time window. When `true`, only return native reposts. When `true`, enable X safe-search filtering. When `true`, only return news results. When `true`, only return tweets from verified authors. Set `include`, `exclude`, or `only` for reply tweets. Set `include`, `exclude`, or `only` for reposts. Set `include`, `exclude`, or `only` for quote tweets. Exact text that must appear in the tweet. Words or quoted phrases to exclude from returned tweets. Use commas or whitespace between values. Words or quoted phrases where at least 1 term must appear. Use commas or whitespace between values. Only include tweets matching these hashtags. Use commas or whitespace between values. The `#` prefix is optional. Only include tweets matching these cashtags. Use commas or whitespace between values. The `$` prefix is optional. URL substring or domain that must appear in tweet URL entities. Filter to tweets in this conversation thread. Only include replies to this tweet ID. Filter to quote tweets of this tweet ID. Filter to retweets of this tweet ID. ## Which tweet engagement endpoint? Use `GET /x/tweets/{id}/quotes` for tweet rows that quote one source tweet. Use [`GET /x/tweets/{id}/replies`](/api-reference/x/tweet-replies) when you need reply tweet rows under the source tweet. Use [`GET /x/tweets/{id}/retweeters`](/api-reference/x/retweeters) for user profiles that reposted one source tweet. Use [`GET /x/tweets/{id}/favoriters`](/api-reference/x/favoriters) for user profiles that liked one source tweet. Use [`Create extraction`](/api-reference/extractions/create) with `toolType=quote_extractor` when you need a saved job or CSV, JSON, or XLSX export. Use [`Search tweets`](/api-reference/x/search-tweets) when you need keyword, operator, or structured-filter discovery across many source tweets. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of quote tweets. **Tweet object fields:** This value is the tweet ID. This value contains the tweet text. This value identifies the tweet type when available. This value contains the ISO 8601 creation timestamp. Whether this is a Note Tweet. Omitted if unavailable. Whether the tweet is a reply. Omitted if unavailable. This ID identifies the tweet receiving the reply. User ID being replied to. Omitted if unavailable. Username being replied to. Omitted if unavailable. Conversation thread ID. Omitted if unavailable. This value records the like count when available. This value records the repost count when available. This value records the reply count when available. This value records the quote tweet count when available. This value records the view count when available. This value records the bookmark count when available. Permalink URL on X. Omitted if unavailable. The tweet's language code appears here when X returns it. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. X can return paid partnership and AI-generated media labels here. Paid partnership state appears in `advertising.isPaidPromotion`. AI media state appears in `aiGenerated.hasAiGeneratedMedia`. X may omit this object. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author handle without `@`. Author display name. This value records the author's follower count when available. Whether the author is verified. Omitted if unavailable. Author profile image URL. Omitted if unavailable. This array contains media attachments when available. **Media object fields:** Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Cursor for the next page. ### 401 Unauthenticated Anonymous requests receive `WWW-Authenticate: Bearer`. This is not a Payment challenge. ### 402 Payment required Account keys receive account options. A guest wallet receives a checkout option. Confirm any payment action. **Related:** [Tweet replies](/api-reference/x/tweet-replies) · [Tweet thread](/api-reference/x/tweet-thread) · [Retweeters](/api-reference/x/retweeters) · [Tweet favoriters](/api-reference/x/favoriters) # Twitter API Get Replies to a Tweet & Author Fields Source: https://docs.xquik.com/api-reference/x/tweet-replies GET /x/tweets/{id}/replies Use a Twitter API to get replies to a tweet with cursors, author profiles, engagement metrics, media, time filters, moderation rows, and every error response. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA", "nested_replies": [ { "id": "1234567890", "text": "Just launched our new feature!", "likeCount": 42, "retweetCount": 5, "replyCount": 3 } ], "diagnostic": { "complete": false, "reportedReplyCount": 0, "targetDirectReplies": 0, "uniqueDirectReplies": 0, "coveragePercentage": 0 } } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "replies_incomplete", "message": "Replies are incomplete. Retry complete mode later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "Maximum coverage is busy. Retry shortly." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) 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. 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`. 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. Finished, expired, superseded, or identity-mismatched cursors return `410 coverage_cursor_gone`. The response omits `Retry-After`. Restart without a cursor. Deduplicate restarted results by `id`. Malformed cursors return `400 invalid_coverage_cursor`. Restart without them. Get tweet replies returns reply tweets for one X post by numeric tweet ID. Use it for conversation analysis, support queues, moderation review, giveaway audits, and agent handoffs. Reply visibility depends on X. Complete mode returns `424 replies_incomplete` below 80% direct-reply coverage. Inspect the returned diagnostic before retrying. Never treat missing rows as proof that a user did not reply. The [tweet, profile, media, and reply field guide](/guides/tweet-profile-api-fields) explains reply coverage and optional response fields. ## Twitter API Reply Questions ### How Does the Twitter API Get Replies to a Tweet? Start with the original tweet's numeric ID. Send it through this endpoint's path parameter. Automatic pages accept `pageSize` from `1` through `300`. Standard pages accept `1` through `100`. The response contains reply tweets, author profiles, and engagement counts. Store `next_cursor` after writing every page. Continue only while `has_next_page` remains `true`. ### How Complete Is a Tweet Reply Collection? X controls which public replies each request exposes. Protected, deleted, hidden, or unavailable replies may not appear. `mode=complete` combines timelines, rankings, cursors, hidden branches, parent windows, and search. Direct replies match `inReplyToId` to the source tweet. Exclude `nested_replies` from direct coverage. Trust `diagnostic.complete`. ### How Can a Team Analyze or Moderate Replies? Store each reply ID, text, author ID, username, creation time, likes, reposts, quotes, views, bookmarks, and media. Create one moderation row per reply. Apply sentiment or spam labels with your classifier. Send uncertain rows to human reviewers. Xquik does not infer sentiment. ### How Can Support Teams Receive New Reply Alerts? Create an [account monitor](/api-reference/monitors/create) for the relevant profile. Select `tweet.reply` events on the monitor and webhook. Verify every webhook signature. Store each event ID before updating a support ticket. Replay missed events through the events API. Poll this endpoint for conversation backfills. ### Which Reply Fields Should Applications Preserve? Keep stable author IDs separately from mutable usernames. Save `conversationId` and `inReplyToId` for thread joins. Store media URLs, likes, reposts, quotes, views, bookmarks, time filters, page size, and cursors for audits and retries. ### How Can Applications Control Reply API Costs? Each returned reply costs 1 credit. Select a page size that fits your storage process. Bound support periods with `sinceTime` and `untilTime`. Save each page before requesting another cursor. Use `reply_extractor` for fixed `resultsLimit` exports. ```bash cURL theme={null} # First page of replies curl "https://xquik.com/api/v1/x/tweets/1893456789012345678/replies" \ -H "x-api-key: xq_your_api_key_here" | jq # Resume with the previous next_cursor curl -G "https://xquik.com/api/v1/x/tweets/1893456789012345678/replies" \ --data-urlencode "cursor=DAACCgACGE..." \ -H "x-api-key: xq_your_api_key_here" | jq # Bound a campaign or moderation window curl -G "https://xquik.com/api/v1/x/tweets/1893456789012345678/replies" \ --data-urlencode "sinceTime=1777392000" \ --data-urlencode "untilTime=1777478400" \ -H "x-api-key: xq_your_api_key_here" | jq # Request advanced nested-reply diagnostics curl -G "https://xquik.com/api/v1/x/tweets/1893456789012345678/replies" \ --data-urlencode "mode=complete" \ --data-urlencode "limit=25000" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const tweetId = "1893456789012345678"; let pageCursor = ""; for (let pageCount = 0; pageCount < 3; pageCount += 1) { const params = pageCursor === "" ? "" : `?${new URLSearchParams({ cursor: pageCursor })}`; const response = await fetch( `https://xquik.com/api/v1/x/tweets/${tweetId}/replies${params}`, { headers: { "x-api-key": "xq_your_api_key_here" } }, ); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const replyRows = page.tweets.map((reply) => ({ parent_tweet_id: tweetId, reply_id: reply.id, text: reply.text, author_id: reply.author?.id ?? null, author_username: reply.author?.username ?? null, created_at: reply.createdAt ?? null, in_reply_to_id: reply.inReplyToId ?? null, conversation_id: reply.conversationId ?? null, like_count: reply.likeCount ?? null, media_urls: (reply.media ?? []).map((item) => item.mediaUrl).filter(Boolean), page_index: pageCount, page_cursor: pageCursor, next_cursor: page.next_cursor, has_next_page: page.has_next_page, })); for (const row of replyRows) process.stdout.write(`${JSON.stringify(row)}\n`); if (!page.has_next_page || page.next_cursor === "") break; pageCursor = page.next_cursor; } ``` ```python Python theme={null} import json import requests tweet_id = "1893456789012345678" page_cursor = "" for page_index in range(3): params = {"cursor": page_cursor} if page_cursor else {} response = requests.get( f"https://xquik.com/api/v1/x/tweets/{tweet_id}/replies", params=params, headers={"x-api-key": "xq_your_api_key_here"}, ) page = response.json() if not response.ok: raise RuntimeError(page) for reply in page["tweets"]: reply_row = { "parent_tweet_id": tweet_id, "reply_id": reply["id"], "text": reply["text"], "author_id": (reply.get("author") or {}).get("id"), "author_username": (reply.get("author") or {}).get("username"), "created_at": reply.get("createdAt"), "in_reply_to_id": reply.get("inReplyToId"), "conversation_id": reply.get("conversationId"), "like_count": reply.get("likeCount"), "media_urls": [ item["mediaUrl"] for item in reply.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(reply_row, separators=(",", ":"))) if not page["has_next_page"] or not page["next_cursor"]: break page_cursor = page["next_cursor"] ``` ## Direct Replies Handoff Use `GET /x/tweets/{id}/replies` when a support, community, moderation, giveaway, or agent workflow needs reply rows as JSON. The examples above write JSON Lines rows with `parent_tweet_id`, `reply_id`, `text`, author IDs and usernames, thread joins, media URLs, and cursor fields. Each output line ties one reply to the requested tweet. Workers can restart after storing the latest response page. The moderation table below adds follower, verification, timing, and engagement projections. A `424 replies_incomplete` response still contains collected direct and nested replies. Inspect its diagnostic before retrying. Keep only rows whose `inReplyToId` matches the requested tweet for direct-reply workflows. Use [`reply_extractor`](/guides/tweet-replies-export) instead when a team needs an estimate, durable extraction ID, stored result pages, or CSV, JSON, and XLSX downloads after completion. Call `GET /x/tweets/{id}/replies` when queues, agents, or dashboards need current JSON rows and can store `next_cursor`. Run `reply_extractor` for estimates, job status, stored pages, and downloadable reply files. `sinceTime` and `untilTime` are Unix timestamps in seconds. Use them to bound moderation windows, campaign periods, or giveaway audit ranges. Direct replies calls use the default paid page size; use `reply_extractor` with `resultsLimit` when you need a predictable file export cap. Direct replies cost 1 credit per tweet returned. Low balances can reduce standard pages. Zero affordable results return `402 insufficient_credits`. Retry `429` with `Retry-After`. Retry `502` after a short backoff. Retry `503` after its stated delay. For `424 replies_incomplete`, inspect `diagnostic.recommendedFallback`. ## Build a Reply Moderation Table Store one row per reply. Keep the parent Tweet ID and conversation ID beside the reply so support, moderation, campaign, and giveaway reviews can reconstruct each conversation branch. | Reply column | Response source | Review use | | ------------------ | --------------------------- | --------------------------------------------------------- | | `parent_tweet_id` | Requested path ID | Join every reply to the source tweet. | | `reply_id` | `tweets[].id` | De-duplicate replies and reference one reply safely. | | `text` | `tweets[].text` | Search, label, and display the reply body. | | `author_id` | `tweets[].author.id` | Keep a stable author key when usernames change. | | `author_username` | `tweets[].author.username` | Show the current reply author handle. | | `author_followers` | `tweets[].author.followers` | Add audience context without using it as an identity key. | | `author_verified` | `tweets[].author.verified` | Preserve the observed verification state. | | `created_at` | `tweets[].createdAt` | Sort replies inside the campaign or moderation window. | | `in_reply_to_id` | `tweets[].inReplyToId` | Join nested replies to their immediate parent. | | `conversation_id` | `tweets[].conversationId` | Group replies that belong to the same X conversation. | | `like_count` | `tweets[].likeCount` | Prioritize replies with stronger visible engagement. | | `media_urls` | `tweets[].media[].mediaUrl` | Preserve image or video evidence attached to the reply. | | Collection checkpoint | Stored value | Recovery rule | | ---------------------------- | ------------------------------------------ | ------------------------------------------------------------------ | | Page identity | `page_index` and `page_cursor` | Save both before writing the next reply page. | | Resume cursor | `next_cursor` | Send it as `cursor` only when `has_next_page` is `true`. | | Time window | `sinceTime` and `untilTime` | Keep both time values unchanged when retrying. | | Completion state | `has_next_page` plus the terminal cursor | Mark the export complete only after the final page. | | Incomplete complete-mode run | `424 replies_incomplete` plus `diagnostic` | Preserve rows, inspect evidence, and follow `recommendedFallback`. | ## Which replies endpoint? * Use `GET /api/v1/x/tweets/{id}/replies` for one tweet's replies as JSON rows. * Use [`reply_extractor`](/guides/tweet-replies-export) when you need saved CSV, JSON, or XLSX exports. * Use `GET /api/v1/x/tweets/search` when you need keyword, operator, structured-filter, or `queryType` search. * Use `GET /api/v1/x/tweets/{id}/thread` when you need ordered thread context around a tweet. ## Path parameters Numeric X tweet ID. Pass the source tweet whose replies you want to retrieve. ## Query parameters Optional advanced override. Omit it for automatic maximum direct-reply coverage. Use `standard` for legacy pagination. Use `complete` for nested replies and detailed diagnostics. Complete mode accepts only `limit`. Maximum combined replies in complete mode. Use `1` through `25000`. Complete mode defaults to `25000`. Automatic pages accept `1` through `300`. Standard pages accept `1` through `100`. Automatic pages accept `1` through `300`. Standard pages accept `1` through `100`. Omit this field in complete mode. Deprecated aliases remain. Pass `next_cursor` back unchanged. New Xquik cursors resume automatic coverage. Existing unprefixed cursors keep legacy behavior. Unix timestamp in seconds. Only return replies after this time when a poller, moderation queue, or campaign report needs a bounded window. Unix timestamp in seconds. Only return replies before this time. Pair with `sinceTime` for closed campaign, support, or audit windows. In complete mode, select `all`, `direct`, or `nested` replies. In complete mode, set the maximum reply depth from the source post. In complete mode, sort by `relevance`, `latest`, `oldest`, or `likes`. In complete mode, exclude replies from the source-post author. In complete mode, include the source post and count it toward `limit`. In complete mode, only return replies containing media. ### Tweet result filters These filters apply to automatic and standard pagination. They keep the same parent tweet and filter rows after retrieval. Selective filters can return fewer rows. Remove every filter before requesting complete mode. Filter to tweets authored by this username. The `@` prefix is optional. Filter to replies directed to this username. Filter to tweets that mention this username. Only include tweets with this language code, such as `en`, `tr`, or `es`. Filter to tweets created on or after this date or timestamp. Filter to tweets created before this date or timestamp. A `YYYY-MM-DD` value includes the whole day before the boundary. Filter by attached media or links. Values: `images`, `videos`, `gifs`, `media`, `links`, `none`. Only include tweets meeting this minimum like count. Only include tweets meeting this minimum repost count. Minimum reply count. Minimum quote count. Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Tweets without a count pass this filter. Maximum Tweet repost count. Tweets without a count pass this filter. Maximum Tweet reply count. Tweets without a count pass this filter. Maximum Tweet quote count. Tweets without a count pass this filter. When `true`, only return Tweets from Blue-verified authors. Match the Tweet card name. Match the source application. Exclude Tweets from this source application. Match latitude, longitude, and radius in X search syntax. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match this place name. Set the radius for the `near` filter. Match Tweets inside this recent time window. When `true`, only return native reposts. When `true`, enable X safe-search filtering. When `true`, only return news results. When `true`, only return tweets from verified authors. Set `include`, `exclude`, or `only` for reply tweets. Set `include`, `exclude`, or `only` for reposts. Set `include`, `exclude`, or `only` for quote tweets. Exact text that must appear in the tweet. Words or quoted phrases to exclude from returned tweets. Use commas or whitespace between values. Words or quoted phrases where at least 1 term must appear. Use commas or whitespace between values. Only include tweets matching these hashtags. Use commas or whitespace between values. The `#` prefix is optional. Only include tweets matching these cashtags. Use commas or whitespace between values. The `$` prefix is optional. URL substring or domain that must appear in tweet URL entities. Filter to tweets in this conversation thread. Only include replies to this tweet ID. Filter to quote tweets of this tweet ID. Filter to retweets of this tweet ID. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of reply tweets. This value is the tweet ID. This value contains the tweet text. This value identifies the tweet type when available. This value contains the ISO 8601 creation timestamp. Whether this is a Note Tweet. Omitted if unavailable. Whether the tweet is a reply. This ID identifies the tweet receiving the reply. User ID being replied to. Omitted if unavailable. Username being replied to. Omitted if unavailable. Conversation thread ID. Omitted if unavailable. This value records the like count when available. This value records the repost count when available. This value records the reply count when available. This value records the quote tweet count when available. This value records the view count when available. This value records the bookmark count when available. Permalink URL on X. Omitted if unavailable. The tweet's language code appears here when X returns it. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. X can return paid partnership and AI-generated media labels here. Paid partnership state appears in `advertising.isPaidPromotion`. AI media state appears in `aiGenerated.hasAiGeneratedMedia`. X may omit this object. Tweet author profile. Omitted if unavailable. Author user ID. Author handle without `@`. Author display name. This value records the author's follower count when available. Whether the author is verified. Omitted if unavailable. Author profile image URL. Omitted if unavailable. This array contains media attachments when available. Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Cursor for the next page. Nested replies returned by complete mode. Exclude them from direct coverage. Complete-mode coverage evidence. Omitted from standard responses. Whether direct coverage met the target without truncation. Reply count reported on the source tweet. Minimum direct replies required for the coverage target. Unique replies whose parent matches the requested tweet. Unique direct replies divided by the reported count. Nested replies excluded from direct coverage. Pages attempted across every collection strategy. Strategy names, page counts, contributions, and stop reasons. Duplicate tweet IDs removed across strategies. Cursor requests that failed. Repeated cursors rejected to prevent loops. Empty pages rejected for making no progress. Malformed response items rejected. Tweets rejected because they belong elsewhere. Expected X modules or fields that were unavailable. Recommended action when coverage remains incomplete. Field-presence counts across collected direct replies. Includes `totalReplies`, `text`, `author`, `createdAt`, `language`, `url`, `entities`, `media`, `article`, `card`, `communityNote`, `quotedOrRepostedTweet`, and `engagementCounts`. Whether the requested limit truncated safe results. ### 400 Invalid Tweet ID The response uses `invalid_tweet_id`. Replace the path value with a numeric Tweet ID before retrying. ### 401 Unauthenticated Anonymous requests get `WWW-Authenticate: Bearer` and a guest wallet checkout action. This is not a Payment challenge. ### 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 The read service returned an error. Retry after a short delay. ### 429 Rate Limit Exceeded Your tier rate limit was exceeded. Wait for the `Retry-After` header before retrying. ### 424 Replies Incomplete The response preserves collected rows and diagnostic evidence. Trust `diagnostic.complete`. Follow `recommendedFallback` before retrying. ### 503 Complete Reply Extraction Busy Wait for the `Retry-After` duration before repeating complete mode. **Related:** [Tweet Replies Export Workflow](/guides/tweet-replies-export) when you need saved CSV, JSON, or XLSX files, [Tweet Quotes](/api-reference/x/tweet-quotes), [Tweet Thread](/api-reference/x/tweet-thread), [Retweeters](/api-reference/x/retweeters), and [Favoriters](/api-reference/x/favoriters). # Tweet Thread API, Conversation Export & Authors Source: https://docs.xquik.com/api-reference/x/tweet-thread GET /x/tweets/{id}/thread Retrieve a complete tweet thread around one tweet with authors, reply relationships, text, media, engagement metrics, and cursors. See request fields. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) Get tweet thread returns tweet rows in the conversation thread around one source tweet. It is also useful as a tweet thread API, X thread API, Twitter thread API, conversation thread API, or thread context endpoint. The canonical endpoint remains `GET /api/v1/x/tweets/{id}/thread`. ```bash First page theme={null} curl "https://xquik.com/api/v1/x/tweets/1893456789012345678/thread" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```bash Next page theme={null} curl -G https://xquik.com/api/v1/x/tweets/1893456789012345678/thread \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const tweetId = "1893456789012345678"; const cursor = process.env.XQUIK_CURSOR ?? ""; const params = new URLSearchParams(); if (cursor !== "") { params.set("cursor", cursor); } const query = params.toString(); const url = `https://xquik.com/api/v1/x/tweets/${tweetId}/thread${query ? `?${query}` : ""}`; const response = await fetch(url, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const nextCursor = data.has_next_page ? data.next_cursor : null; const threadRows = data.tweets.map((tweet) => { const author = tweet.author ?? {}; return { source_tweet_id: tweetId, thread_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, in_reply_to_id: tweet.inReplyToId ?? null, media_urls: tweet.media?.map((item) => item.mediaUrl).filter(Boolean) ?? [], }; }); const checkpoint = { source_tweet_id: tweetId, next_cursor: nextCursor }; for (const row of threadRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } process.stdout.write(`${JSON.stringify({ checkpoint })}\n`); ``` ```python Python theme={null} import json import requests tweet_id = "1893456789012345678" cursor = "" params = {"cursor": cursor} if cursor else None response = requests.get( f"https://xquik.com/api/v1/x/tweets/{tweet_id}/thread", params=params, headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() next_cursor = data["next_cursor"] if data["has_next_page"] else None thread_rows = [] for tweet in data["tweets"]: author = tweet.get("author") or {} thread_rows.append( { "source_tweet_id": tweet_id, "thread_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"), "in_reply_to_id": tweet.get("inReplyToId"), "media_urls": [ item["mediaUrl"] for item in tweet.get("media", []) if item.get("mediaUrl") ], } ) checkpoint = {"source_tweet_id": tweet_id, "next_cursor": next_cursor} for row in thread_rows: print(json.dumps(row)) print(json.dumps({"checkpoint": checkpoint})) ``` The Node.js and Python snippets write JSON Lines thread rows plus a separate checkpoint instead of raw response pages. Persist each mapped row and the latest `next_cursor` so a support timeline, research job, moderation queue, or agent handoff can resume from the last completed page without duplicate rows. ## Direct tweet thread handoff Use `GET /api/v1/x/tweets/{id}/thread` when a workflow needs ordered thread context as durable rows around one tweet. Store `source_tweet_id`, `thread_tweet_id`, `text`, `author_id`, `author_username`, `author_name`, `author_followers`, `author_verified`, `author_profile_picture`, `created_at`, `conversation_id`, `in_reply_to_id`, media URLs, and a separate `next_cursor` checkpoint for downstream jobs. Store `tweets[]` as the thread context rows returned for one source tweet. Store `tweets[].id` as `thread_tweet_id` with `source_tweet_id` for idempotent imports. Store `conversationId`, `inReplyToId`, `inReplyToUserId`, and `inReplyToUsername` to rebuild thread structure. Store `tweets[].author.id`, `username`, `name`, `followers`, `verified`, and `profilePicture` for review, CRM, or research tools. Store `media[].mediaUrl`, `entities`, `quoted_tweet`, and `retweeted_tweet` when returned to preserve attached context. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. Use `tweets.length`, not a requested page size, for row counts. Low balances can return fewer rows. Use `thread_extractor` when you need a saved extraction job or CSV, JSON, or XLSX export. Direct tweet thread reads cost 1 credit per tweet returned. Low credit balances can return fewer tweets than a full page; zero affordable results return `402 insufficient_credits`. ## Reconstruct an ordered tweet thread Use this route when several connected posts form one authored sequence. Start from a known tweet ID. Store each tweet ID, text, author, creation time, engagement counts, media, and thread position. Keep the starting tweet ID with the complete result. Render posts in the returned thread order. Do not sort by engagement counts. Preserve media and quote relationships inside each post. Thread results can support long-form reading, approved archiving, or conversation context. A reply from another author may belong to a different conversation path. Use tweet replies for responses under one post. Use quote tweets for external commentary. Use exact tweet lookup when only one post is required. ## Path parameters Tweet ID (numeric string). ## Query parameters Pagination cursor from `next_cursor` in a previous response. Omit for the first page. Pass a cursor only when `has_next_page` is true. Tweets per page. Range: `1-100`. Defaults to `20`. ## Which thread endpoint? Use `GET /x/tweets/{id}/thread` for conversation thread context around one tweet. Use [`GET /x/tweets/{id}/replies`](/api-reference/x/tweet-replies) when you need reply tweet rows under one source tweet. Use [`GET /x/tweets/{id}/quotes`](/api-reference/x/tweet-quotes) for tweet rows that quote one source tweet. Use [`GET /x/tweets/search`](/api-reference/x/search-tweets) when you need keyword, operator, or structured-filter discovery across many tweets. Use [`Create extraction`](/api-reference/extractions/create) with `toolType=thread_extractor` when you need a saved job or CSV, JSON, or XLSX export. Use [`Get tweet`](/api-reference/x/get-tweet) when you only need one tweet object by ID. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of tweets in the thread, ordered chronologically. **Tweet object fields:** Tweet ID. Tweet text. Tweet type. Omitted if unavailable. ISO 8601 creation timestamp. Whether this is a Note Tweet. Omitted if unavailable. Like count. Omitted if unavailable. Retweet count. Omitted if unavailable. Reply count. Omitted if unavailable. Quote tweet count. Omitted if unavailable. View count. Omitted if unavailable. Bookmark count. Omitted if unavailable. Permalink URL on X. Omitted if unavailable. Tweet language code. Omitted if unavailable. Whether this tweet is a reply in the thread. Tweet ID being replied to. Omitted if not a reply. User ID being replied to. Omitted if unavailable. Username being replied to. Omitted if unavailable. Thread conversation ID. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author handle without `@`. Author display name. Omitted if unavailable. Follower count. Omitted if unavailable. Whether the author is verified. Omitted if unavailable. Author profile image URL. Omitted if unavailable. Media attachments. Omitted when the tweet has no media. **Media object fields:** Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Cursor for the next page. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "Thread starts here...", "createdAt": "2026-03-27T10:00:00.000Z", "author": { "id": "9876543210", "username": "xquik", "name": "Xquik", "followers": 12000, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg" } } ], "has_next_page": false, "next_cursor": "" } ``` ### 400 Invalid tweet ID ```json theme={null} { "error": "invalid_tweet_id" } ``` ### 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 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Tweet replies](/api-reference/x/tweet-replies) · [Quote tweets](/api-reference/x/tweet-quotes) · [Retweeters](/api-reference/x/retweeters) · [Create extraction](/api-reference/extractions/create) # Twitter Profile Search, Lookup & Audience Counts Source: https://docs.xquik.com/api-reference/x/twitter-profile-lookup GET /x/users/{id} Search a Twitter or X profile by username or user ID. Retrieve the bio, follower and following counts, verification status, location, and links. See costs. ```json theme={null} { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk", "description": "CEO of Tesla, SpaceX, and X", "followers": 150000000, "following": 500, "verified": true } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required. Provide a valid API key or bearer token." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "user_not_found", "message": "X user not found. Check the username." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
**1 credit per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Direct [MPP](/mpp/machine-payments-protocol): USD 0.00015 per call See [Read Data Richness](/guides/tweet-profile-api-fields) for every optional profile field. Xquik omits fields that X does not supply. ```bash cURL theme={null} curl https://xquik.com/api/v1/x/users/username \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const username = "username"; const response = await fetch(`https://xquik.com/api/v1/x/users/${username}`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const profileRow = { user_id: data.id, username: data.username, display_name: data.name, bio: data.description ?? null, follower_count: data.followers ?? null, following_count: data.following ?? null, verified: data.verified ?? false, verified_type: data.verifiedType ?? null, profile_url: data.url ?? null, profile_image_url: data.profilePicture ?? null, created_at: data.createdAt ?? null, unavailable_reason: data.unavailableReason ?? null, }; process.stdout.write(`${JSON.stringify(profileRow)}\n`); ``` ```python Python theme={null} import json import requests response = requests.get( "https://xquik.com/api/v1/x/users/username", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() profile_row = { "user_id": data["id"], "username": data["username"], "display_name": data["name"], "bio": data.get("description"), "follower_count": data.get("followers"), "following_count": data.get("following"), "verified": data.get("verified", False), "verified_type": data.get("verifiedType"), "profile_url": data.get("url"), "profile_image_url": data.get("profilePicture"), "created_at": data.get("createdAt"), "unavailable_reason": data.get("unavailableReason"), } print(json.dumps(profile_row)) ``` ```go Go theme={null} package main import ( "encoding/json" "io" "log" "net/http" "os" ) func main() { username := "username" req, err := http.NewRequest("GET", "https://xquik.com/api/v1/x/users/"+username, nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", "xq_your_api_key_here") resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } var user map[string]any if err := json.Unmarshal(body, &user); err != nil { log.Fatal(err) } row := map[string]any{ "user_id": user["id"], "username": user["username"], "display_name": user["name"], "bio": user["description"], "follower_count": user["followers"], "following_count": user["following"], "verified": user["verified"], "verified_type": user["verifiedType"], "profile_url": user["url"], "profile_image_url": user["profilePicture"], "created_at": user["createdAt"], "unavailable_reason": user["unavailableReason"], } if err := json.NewEncoder(os.Stdout).Encode(row); err != nil { log.Fatal(err) } } ``` Use `GET /x/users/{id}` when a workflow needs one durable profile row for CRM enrichment, lead scoring, account monitoring, or support triage. Store `user_id`, `username`, `display_name`, `bio`, `follower_count`, `following_count`, `verified`, `verified_type`, `profile_url`, `profile_image_url`, `created_at`, and `unavailable_reason` instead of logging the full lookup response. Store `id` as `user_id` plus `username`, `name`, `description`, `followers`, `following`, `verified`, and `verifiedType`. Use a numeric user ID for durable joins. Use username lookup when the workflow starts from a handle. Store `unavailable` and `unavailableReason` when returned, so retries, support triage, and CRM syncs distinguish missing accounts from unavailable profiles. Use the returned `id` for followers, following, timelines, media, DMs, follow checks, and monitor setup. ## Store a Twitter profile lookup Keep the numeric X user ID as the stable key. Profile usernames, names, bios, counts, verification, locations, links, and images can change between lookups. | Profile column | Response field | CRM or warehouse rule | | -------------------- | ------------------- | ---------------------------------------------------------------- | | `x_user_id` | `id` | Use as the stable profile upsert and relationship key. | | `x_username` | `username` | Store the current handle without using it as the durable key. | | `display_name` | `name` | Preserve the public profile name observed during lookup. | | `bio` | `description` | Store the profile bio when X returns it. | | `follower_count` | `followers` | Preserve the observed follower count with the lookup time. | | `following_count` | `following` | Preserve the observed following count with the lookup time. | | `verified` | `verified` | Record the observed verification state. | | `verified_type` | `verifiedType` | Distinguish returned business or government verification types. | | `location` | `location` | Preserve the public profile location when supplied. | | `profile_url` | `url` | Keep the public website link from the profile. | | `profile_image_url` | `profilePicture` | Use the returned image in profile previews and review queues. | | `account_created_at` | `createdAt` | Preserve the account creation timestamp when available. | | `unavailable` | `unavailable` | Separate an unavailable profile from a successful active lookup. | | `unavailable_reason` | `unavailableReason` | Route suspended, deactivated, or otherwise unavailable profiles. | | Next workflow | Stable input | Route | | ----------------------------- | --------------------------------------------- | ------------------------- | | Follower export | `id` | `/x/users/{id}/followers` | | Following export | `id` | `/x/users/{id}/following` | | Profile timeline | `id` | `/x/users/{id}/tweets` | | Profile replies | `id` | `/x/users/{id}/replies` | | Profile media | `id` | `/x/users/{id}/media` | | One follow relationship | Source and target profile IDs | `/x/followers/check` | | Saved profile or audience job | Numeric profile ID in the selected tool input | `/extractions` | ## Path parameters User ID or username (without `@`). ## Which user endpoint? Use `GET /x/users/{id}` for one profile row by username or numeric user ID. Use [`GET /x/users/search`](/api-reference/x/search-users) when the workflow starts from a name, keyword, or partial handle. Use [`GET /x/users/{id}/tweets`](/api-reference/x/user-tweets) for posts from one profile. Use [`GET /x/users/{id}/followers`](/api-reference/x/followers) when the next step needs audience rows. Use [`GET /x/followers/check`](/api-reference/x/check-follower) when the workflow only needs one source-target relationship. Use [`Create extraction`](/api-reference/extractions/create). Create saved CSV/JSON/XLSX jobs. Export followers, following, timelines, media, or search. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` authenticates `paid_reads` guest keys. Direct MPP uses the `Payment ...` credential. Get it from the `WWW-Authenticate: Payment` challenge. ## Response ### 200 OK User ID. X username. Display name. Profile bio. Omitted if empty. Follower count. Omitted if unavailable. Following count. Omitted if unavailable. Whether the user is verified. Omitted if unavailable. Profile picture URL. Omitted if unavailable. Profile location. Omitted if empty. ISO 8601 account creation timestamp. Omitted if unavailable. Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines (lists). Omitted if unavailable. Whether the user is a Twitter translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. IDs of pinned tweets. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable (suspended, deactivated). Omitted if available. Reason the account is unavailable. Omitted if available. Verification type (e.g. `Business`, `Government`). Omitted if not verified or standard blue check. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. Role within a returned community context. Omitted outside community results. ```json theme={null} { "id": "987654321", "username": "username", "name": "Xquik", "description": "X real-time data platform", "followers": 10000, "following": 500, "verified": true, "profilePicture": "https://pbs.twimg.com/profile_images/xquik/photo.jpg", "coverPicture": "https://pbs.twimg.com/profile_banners/xquik/cover.jpg", "location": "San Francisco", "url": "https://xquik.com", "createdAt": "2020-01-15T00:00:00.000Z", "statusesCount": 5000, "mediaCount": 120, "favouritesCount": 430, "isAutomated": false, "possiblySensitive": false, "hasCustomTimelines": false, "pinnedTweetIds": ["1234567890"] } ``` ### 400 Invalid username ```json theme={null} { "error": "invalid_username" } ``` The provided username is empty or not a valid format. ### 401 Unauthenticated ```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. Anonymous calls receive a direct MPP `WWW-Authenticate: Payment` challenge plus a guest wallet creation action. No checkout starts automatically. Confirm any payment action. ### 404 User not found ```json theme={null} { "error": "user_not_found" } ``` The X user does not exist. Check the username. ### 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 } ``` Your tier rate limit was exceeded. 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. **Next steps:** [Check Follower](/api-reference/x/check-follower) to verify follow relationships, or [Search Tweets](/api-reference/x/search-tweets) to find tweets from this user. # Twitter User Likes API & Liked Tweet Export Source: https://docs.xquik.com/api-reference/x/user-likes GET /x/users/{id}/likes Retrieve tweets liked by one X user with authors, full text, attached media, replies, reposts, quotes, views, and cursor pagination. See request fields. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
* 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)
## Choose Liked Tweets Use this route for tweets liked by one user. The output is tweet rows with engagement and pagination. Use media or timeline routes when authored posts are required. 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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) ```bash cURL theme={null} curl https://xquik.com/api/v1/x/users/44196397/likes \ -H "x-api-key: xq_your_api_key_here" | jq # Page 2 curl -G https://xquik.com/api/v1/x/users/44196397/likes \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const userId = "44196397"; let pageCursor = ""; for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { const params = pageCursor === "" ? "" : `?${new URLSearchParams({ cursor: pageCursor })}`; const response = await fetch( `https://xquik.com/api/v1/x/users/${userId}/likes${params}`, { headers: { "x-api-key": "xq_your_api_key_here" } }, ); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const likedRows = page.tweets.map((tweet) => ({ liked_by_user_id: userId, liked_tweet_id: tweet.id, text: tweet.text, tweet_url: tweet.url ?? null, created_at: tweet.createdAt ?? null, 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, 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 likedRows) process.stdout.write(`${JSON.stringify(row)}\n`); if (!page.has_next_page || page.next_cursor === "") break; pageCursor = page.next_cursor; } ``` ```python Python theme={null} import json import requests user_id = "44196397" page_cursor = "" 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}/likes", 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"]: liked_row = { "liked_by_user_id": user_id, "liked_tweet_id": tweet["id"], "text": tweet["text"], "tweet_url": tweet.get("url"), "created_at": tweet.get("createdAt"), "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"), "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(liked_row, separators=(",", ":"))) if not page["has_next_page"] or not page["next_cursor"]: break page_cursor = page["next_cursor"] ``` ## User likes handoff Use `GET /x/users/{id}/likes` when a CRM, warehouse, recommendation job, or agent needs liked tweet rows from one account. The examples above write JSON Lines rows with the liked-by user, liked tweet ID, text, tweet URL, author ID, username, display name, follower count, verified state, profile image URL, engagement counts, media URLs, and cursor fields so a worker can resume from the last saved `next_cursor`. ## Build a liked-tweet library Use this route to collect tweets liked by one profile. Keep the source user ID with every tweet. A tweet can appear in several users’ liked feeds. Store fields that support review: * Tweet ID, text, author, and creation time. * Like, reply, repost, quote, and view counts. * Photo, video, or animated GIF URLs. * Source profile ID, cursor, and collection time. Liked tweets reflect a public account action. They do not prove agreement, ownership, or endorsement. Preserve the original author separately. For repeated collection, compare tweet IDs between snapshots. New IDs indicate newly observed likes. Missing IDs need careful interpretation because paging depth and source availability can change. Use user media when the workflow needs tweets containing media from the profile itself. Use user tweets for the profile timeline. These feeds answer different questions. ## Compare liked-feed snapshots Label every run as complete or partial. Compare only complete runs collected to the same paging depth. Keep first-seen and last-seen timestamps in your own store. The endpoint returns the current page, not a historical like event log. When a liked tweet disappears, record it as no longer observed. Do not claim the user removed the like unless another verified source confirms that action. Tweet deletion or availability changes can produce the same observation. For review queues, link each row to the source tweet. Keep the liking profile separate from the tweet author. Store reviewer labels outside the tweet response. Use explicit values such as relevant, irrelevant, or needs review. Do not overwrite the original tweet text with notes. Preserve both values in separate fields. | Liked-tweet library column | Response source | Library rule | | -------------------------- | --------------------------- | ----------------------------------------------------------- | | `liked_by_x_user_id` | Resolved source profile ID | Keep the liking profile separate from the tweet author. | | `tweet_id` | `tweets[].id` | Use as the stable liked-tweet upsert key. | | `tweet_url` | `tweets[].url` | Open the original tweet during review. | | `text` | `tweets[].text` | Preserve the original tweet body without reviewer edits. | | `author_id` | `tweets[].author.id` | Keep the original author as a stable identity. | | `author_username` | `tweets[].author.username` | Display the author handle observed during collection. | | `created_at` | `tweets[].createdAt` | Preserve when the tweet was created, not when it was liked. | | `like_count` | `tweets[].likeCount` | Store the visible tweet count observed during collection. | | `media_urls` | `tweets[].media[].mediaUrl` | Preserve image, video, or animated GIF URLs. | | `snapshot_id` | Integration value | Group liked-tweet pages from the same collection run. | | `snapshot_complete` | Integration value | Compare disappearance only across complete snapshots. | | `collected_at` | Integration timestamp | Keep first-seen and last-seen observations auditable. | | Snapshot comparison | Tweet ID condition | Safe interpretation | | ------------------- | --------------------------------------------------------- | ------------------------------------------------------------- | | Newly observed | Present only in the latest complete snapshot | The tweet was newly observed in the liked feed. | | Still observed | Present in both complete snapshots | The tweet remained visible at both collection times. | | No longer observed | Present only in the earlier complete snapshot | Availability changed; do not claim the user removed the like. | | Unknown | Either snapshot is partial or uses different paging depth | Defer classification and preserve both collection records. | ## Path parameters X user ID (numeric string) or username. ## Query parameters Pagination cursor. Pass the `next_cursor` value from the previous response to fetch the next page. Tweets per page. Range: `1-100`. Defaults to `20`. ### Tweet result filters These optional filters apply to `tweets[]` returned by this route. They keep the same liked-tweets target and filter rows after each page is fetched, so selective filters can return fewer rows than an unfiltered page. Filter to tweets authored by this username. The `@` prefix is optional. Filter to replies directed to this username. Filter to tweets that mention this username. Only include tweets with this language code, such as `en`, `tr`, or `es`. Filter to tweets created on or after this date or timestamp. Filter to tweets created before this date or timestamp. A `YYYY-MM-DD` value includes the whole day before the boundary. Filter by attached media or links. Values: `images`, `videos`, `gifs`, `media`, `links`, `none`. Only include tweets meeting this minimum like count. Only include tweets meeting this minimum repost count. Minimum reply count. Minimum quote count. Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Tweets without a count pass this filter. Maximum Tweet repost count. Tweets without a count pass this filter. Maximum Tweet reply count. Tweets without a count pass this filter. Maximum Tweet quote count. Tweets without a count pass this filter. When `true`, only return Tweets from Blue-verified authors. Match the Tweet card name. Match the source application. Exclude Tweets from this source application. Match latitude, longitude, and radius in X search syntax. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match this place name. Set the radius for the `near` filter. Match Tweets inside this recent time window. When `true`, only return native reposts. When `true`, enable X safe-search filtering. When `true`, only return news results. When `true`, only return tweets from verified authors. Set `include`, `exclude`, or `only` for reply tweets. Set `include`, `exclude`, or `only` for reposts. Set `include`, `exclude`, or `only` for quote tweets. Exact text that must appear in the tweet. Words or quoted phrases to exclude from returned tweets. Use commas or whitespace between values. Words or quoted phrases where at least 1 term must appear. Use commas or whitespace between values. Only include tweets matching these hashtags. Use commas or whitespace between values. The `#` prefix is optional. Only include tweets matching these cashtags. Use commas or whitespace between values. The `$` prefix is optional. URL substring or domain that must appear in tweet URL entities. Filter to tweets in this conversation thread. Only include replies to this tweet ID. Filter to quote tweets of this tweet ID. Filter to retweets of this tweet ID. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of liked tweets. **Tweet object fields:** Tweet ID. Tweet text content. Tweet type. Omitted if unavailable. ISO 8601 creation timestamp. Omitted if unavailable. Whether this is a Note Tweet (long-form post). Omitted if unavailable. Like count. Omitted if unavailable. Retweet count. Omitted if unavailable. Reply count. Omitted if unavailable. Quote tweet count. Omitted if unavailable. View count. Omitted if unavailable. Bookmark count. Omitted if unavailable. Permalink URL on X. Omitted if unavailable. Tweet language code. Omitted if unavailable. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. User ID being replied to. Omitted if not a reply. Username being replied to. Omitted if not a reply. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author handle without `@`. Author display name. Follower count. Omitted if unavailable. Whether the author is verified. Omitted if unavailable. Author profile image URL. Omitted if unavailable. Media attachments. Omitted if unavailable. **Media item fields:** Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Opaque cursor for the next page. Empty string when no more results. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "A tweet that was liked", "createdAt": "2026-02-24T10:00:00.000Z", "likeCount": 300, "retweetCount": 80, "url": "https://x.com/user/status/1893456789012345678", "author": { "id": "44196397", "username": "username", "name": "Xquik", "followers": 1200, "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": "User not found or invalid user ID. Check the username or ID." } ``` 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." } ``` ### 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Get user timeline](/api-reference/x/user-tweets) · [User media](/api-reference/x/user-media) · [User mentions timeline](/api-reference/x/user-mentions) # Twitter Media Scraper & Profile Export API Guide Source: https://docs.xquik.com/api-reference/x/user-media GET /x/users/{id}/media Scrape tweets with photos, videos, and GIFs from a Twitter or X profile. Export media posts with authors, metrics, cursors, and timestamps. See costs. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
* 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)
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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) ```bash cURL theme={null} curl https://xquik.com/api/v1/x/users/44196397/media \ -H "x-api-key: xq_your_api_key_here" | jq # Page 2 curl -G https://xquik.com/api/v1/x/users/44196397/media \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const userId = "44196397"; let pageCursor = ""; for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { const params = pageCursor === "" ? "" : `?${new URLSearchParams({ cursor: pageCursor })}`; const response = await fetch( `https://xquik.com/api/v1/x/users/${userId}/media${params}`, { headers: { "x-api-key": "xq_your_api_key_here" } }, ); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const mediaRows = page.tweets.map((tweet) => ({ source_user_id: userId, media_tweet_id: tweet.id, text: tweet.text, tweet_url: tweet.url ?? null, created_at: tweet.createdAt ?? null, 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, 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), media_types: (tweet.media ?? []).map((item) => item.type).filter(Boolean), page_index: pageIndex, page_cursor: pageCursor, next_cursor: page.next_cursor, has_next_page: page.has_next_page, })); for (const row of mediaRows) process.stdout.write(`${JSON.stringify(row)}\n`); if (!page.has_next_page || page.next_cursor === "") break; pageCursor = page.next_cursor; } ``` ```python Python theme={null} import json import requests user_id = "44196397" page_cursor = "" 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}/media", 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"]: media_items = tweet.get("media", []) media_row = { "source_user_id": user_id, "media_tweet_id": tweet["id"], "text": tweet["text"], "tweet_url": tweet.get("url"), "created_at": tweet.get("createdAt"), "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"), "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 media_items if item.get("mediaUrl") ], "media_types": [item["type"] for item in media_items if item.get("type")], "page_index": page_index, "page_cursor": page_cursor, "next_cursor": page["next_cursor"], "has_next_page": page["has_next_page"], } print(json.dumps(media_row, separators=(",", ":"))) if not page["has_next_page"] or not page["next_cursor"]: break page_cursor = page["next_cursor"] ``` ## User media handoff Use `GET /x/users/{id}/media` when a gallery, moderation queue, warehouse, or agent needs recent media tweets from one account. The examples above write JSON Lines rows with the source user, media tweet ID, text, tweet URL, author ID, username, display name, follower count, verified state, profile image URL, engagement counts, `media_urls`, `media_types`, and cursor fields so a worker can resume from the last saved `next_cursor`. ## Which media endpoint? * Use `GET /api/v1/x/users/{id}/media` when every row must be a media tweet from one profile. Store `media_urls`, `media_types`, `media_tweet_id`, `page_cursor`, `next_cursor`, and `has_next_page`. * Use `GET /api/v1/x/users/{id}/tweets` when the sync also needs non-media posts or replies. * Use `GET /api/v1/x/tweets/{id}` when you already know one tweet ID and need its media plus full tweet detail. * Use `POST /api/v1/x/media` only to host or validate a local file or HTTPS media URL before a write. Pass `mediaUrl` to `POST /api/v1/x/tweets`, or pass `mediaId` as the only `media_ids` item for `POST /api/v1/x/dm/{userId}`. ## Build a profile media inventory Use this route for tweets containing photos, videos, or animated GIFs from one profile. Keep the source profile ID with every media item. Record both tweet and media fields: * Tweet ID, text, author, and creation time. * Media type and media URL. * Video variants when present. * Engagement counts and page cursor. One tweet can contain several media items. Preserve the tweet-to-media relationship. Do not flatten several URLs into an ambiguous single value. Use the inventory for asset review, campaign research, or approved archiving. Respect the original publisher and applicable usage rights. Deduplicate tweets by tweet ID. Deduplicate individual assets by their media URL within that tweet. Save each page before advancing the cursor. Use user likes for tweets the profile liked. Use user tweets for the complete profile timeline. Use download media only after selecting a specific asset. ## Hand media into an asset workflow Create one child row per media item. Include the parent tweet ID, media type, source URL, author ID, and collection time. Choose the best available video variant only after inspecting its metadata. Keep the original variant list when later processing may need another format. Do not download every asset during discovery. First filter and approve the relevant tweets. Then call the download route for selected media. Preserve attribution and source links in any archive. The media URL alone does not explain who published the asset or where it appeared. Record download status separately from discovery status. A listed media URL does not mean the asset was downloaded. Keep failed downloads retryable by tweet ID and media URL. Do not rerun the entire profile export for one failed asset. | Media inventory column | Response source | Asset rule | | ---------------------- | -------------------------------- | ------------------------------------------------------------- | | `source_x_user_id` | Resolved profile ID | Keep every media tweet tied to the intended profile. | | `tweet_id` | `tweets[].id` | Use as the parent key for one or more media items. | | `tweet_url` | `tweets[].url` | Preserve the original X post for attribution and review. | | `tweet_text` | `tweets[].text` | Keep the caption or post context beside each asset. | | `author_id` | `tweets[].author.id` | Preserve the original publisher as a stable identity. | | `created_at` | `tweets[].createdAt` | Store the tweet publication time. | | `media_index` | Position inside `tweets[].media` | Preserve the order of several assets on one tweet. | | `media_type` | `tweets[].media[].type` | Separate photo, video, and animated GIF processing. | | `media_url` | `tweets[].media[].mediaUrl` | De-duplicate an asset only within its parent tweet. | | `video_variants` | `tweets[].media[].videoVariants` | Keep every returned rendition until a download is approved. | | `page_cursor` | Request `cursor` | Resume the profile media inventory after stored pages. | | `collected_at` | Integration timestamp | Audit when the media URL and engagement counts were observed. | | Asset decision | Required evidence | Next action | | ------------------------ | -------------------------------------- | ---------------------------------------------------------------- | | Review only | Tweet URL, media URL, author, and text | Keep the remote asset linked to its source tweet. | | Approved image download | Image media type and approved usage | Call the download route for the selected media URL. | | Approved video download | Video variants and selected format | Choose a variant after inspecting bitrate and content type. | | Rejected asset | Reviewer decision and reason | Keep the source record without downloading the file. | | Failed selected download | Tweet ID, media URL, and failure state | Retry only the selected asset, not the entire profile inventory. | ## Path parameters X user ID (numeric string) or username. ## Query parameters Pagination cursor. Pass the `next_cursor` value from the previous response to fetch the next page. Tweets per page. Range: `1-100`. Defaults to `20`. ### Tweet result filters These optional filters apply to `tweets[]` returned by this route. They keep the same media-tweets target and filter rows after each page is fetched, so selective filters can return fewer rows than an unfiltered page. Filter to tweets authored by this username. The `@` prefix is optional. Filter to replies directed to this username. Filter to tweets that mention this username. Only include tweets with this language code, such as `en`, `tr`, or `es`. Filter to tweets created on or after this date or timestamp. Filter to tweets created before this date or timestamp. A `YYYY-MM-DD` value includes the whole day before the boundary. Filter by attached media or links. Values: `images`, `videos`, `gifs`, `media`, `links`, `none`. Only include tweets meeting this minimum like count. Only include tweets meeting this minimum repost count. Minimum reply count. Minimum quote count. Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Tweets without a count pass this filter. Maximum Tweet repost count. Tweets without a count pass this filter. Maximum Tweet reply count. Tweets without a count pass this filter. Maximum Tweet quote count. Tweets without a count pass this filter. When `true`, only return Tweets from Blue-verified authors. Match the Tweet card name. Match the source application. Exclude Tweets from this source application. Match latitude, longitude, and radius in X search syntax. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match this place name. Set the radius for the `near` filter. Match Tweets inside this recent time window. When `true`, only return native reposts. When `true`, enable X safe-search filtering. When `true`, only return news results. When `true`, only return tweets from verified authors. Set `include`, `exclude`, or `only` for reply tweets. Set `include`, `exclude`, or `only` for reposts. Set `include`, `exclude`, or `only` for quote tweets. Exact text that must appear in the tweet. Words or quoted phrases to exclude from returned tweets. Use commas or whitespace between values. Words or quoted phrases where at least 1 term must appear. Use commas or whitespace between values. Only include tweets matching these hashtags. Use commas or whitespace between values. The `#` prefix is optional. Only include tweets matching these cashtags. Use commas or whitespace between values. The `$` prefix is optional. URL substring or domain that must appear in tweet URL entities. Filter to tweets in this conversation thread. Only include replies to this tweet ID. Filter to quote tweets of this tweet ID. Filter to retweets of this tweet ID. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of tweets containing media. **Tweet object fields:** Tweet ID. Tweet text content. Tweet type. Omitted if unavailable. ISO 8601 creation timestamp. Omitted if unavailable. Whether this is a Note Tweet (long-form post). Omitted if unavailable. Like count. Omitted if unavailable. Retweet count. Omitted if unavailable. Reply count. Omitted if unavailable. Quote tweet count. Omitted if unavailable. View count. Omitted if unavailable. Bookmark count. Omitted if unavailable. Permalink URL on X. Omitted if unavailable. Tweet language code. Omitted if unavailable. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. User ID being replied to. Omitted if not a reply. Username being replied to. Omitted if not a reply. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author handle without `@`. Author display name. Follower count. Omitted if unavailable. Whether the author is verified. Omitted if unavailable. Author profile image URL. Omitted if unavailable. Media attachments. Omitted if unavailable. **Media item fields:** Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Opaque cursor for the next page. Empty string when no more results. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "Check out this photo", "createdAt": "2026-02-24T10:00:00.000Z", "likeCount": 200, "viewCount": 15000, "url": "https://x.com/user/status/1893456789012345678", "author": { "id": "44196397", "username": "username", "name": "Xquik", "followers": 1200, "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": "User not found or invalid user ID. Check the username or ID." } ``` 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." } ``` ### 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Get user timeline](/api-reference/x/user-tweets) · [User likes](/api-reference/x/user-likes) · [User mentions timeline](/api-reference/x/user-mentions) # Twitter Mentions Timeline API & Profile Alerts Source: https://docs.xquik.com/api-reference/x/user-mentions GET /x/users/{id}/mentions Retrieve one user's X mentions timeline with cursor pagination, time windows, author fields, engagement metrics, and media. Includes costs and errors. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ```
For the complete documentation index, see llms.txt.
* 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)
Retrieve tweets that mention one profile. Store tweet IDs, authors, timestamps, media, engagement counts, and pagination cursors. 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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit Twitter mentions timeline API returns tweets that mention one X account. Use it for brand mentions, support inboxes, lead routing, and agent handoffs. The canonical route stays `GET /api/v1/x/users/{id}/mentions`. ```bash cURL theme={null} # Username mentions timeline curl "https://xquik.com/api/v1/x/users/username/mentions" \ -H "x-api-key: xq_your_api_key_here" | jq # Numeric user ID mentions timeline curl "https://xquik.com/api/v1/x/users/44196397/mentions" \ -H "x-api-key: xq_your_api_key_here" | jq # Time-bounded mentions window curl -G "https://xquik.com/api/v1/x/users/username/mentions" \ --data-urlencode "sinceTime=1777392000" \ --data-urlencode "untilTime=1777478400" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const userIdOrUsername = "username"; let pageCursor = ""; for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { const params = pageCursor === "" ? "" : `?${new URLSearchParams({ cursor: pageCursor })}`; const response = await fetch( `https://xquik.com/api/v1/x/users/${userIdOrUsername}/mentions${params}`, { headers: { "x-api-key": "xq_your_api_key_here" } }, ); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const mentionRows = page.tweets.map((tweet) => ({ mentioned_user_id_or_username: userIdOrUsername, tweet_id: tweet.id, text: tweet.text, tweet_url: tweet.url ?? null, 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, conversation_id: tweet.conversationId ?? 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, 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 mentionRows) process.stdout.write(`${JSON.stringify(row)}\n`); if (!page.has_next_page || page.next_cursor === "") break; pageCursor = page.next_cursor; } ``` ```python Python theme={null} import json import requests user_id_or_username = "44196397" page_cursor = "" 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}/mentions", 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"]: mention_row = { "mentioned_user_id_or_username": user_id_or_username, "tweet_id": tweet["id"], "text": tweet["text"], "tweet_url": tweet.get("url"), "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"), "conversation_id": tweet.get("conversationId"), "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"), "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(mention_row, separators=(",", ":"))) if not page["has_next_page"] or not page["next_cursor"]: break page_cursor = page["next_cursor"] ``` The Node.js and Python snippets write one JSON Lines row per mentioned tweet. Persist each row with the latest `next_cursor` before requesting the next page. ## Direct mention handoff Use `GET /x/users/{id}/mentions` when a support, community, brand monitoring, lead routing, or agent workflow needs the newest tweets mentioning one account. This mentions timeline endpoint accepts either a username or numeric user ID and returns one JSON page at a time. Use [`mentions`](/api-reference/extractions/create) when you need a saved extraction, estimate, or CSV/JSON/XLSX file export. Store `mentioned_user_id_or_username`, `tweet_id`, `text`, `tweet_url`, `author_id`, `author_username`, `author_name`, `author_followers`, `author_verified`, `author_profile_picture`, `created_at`, `conversation_id`, reply context, engagement counts, media URLs, `page_cursor`, `has_next_page`, and `next_cursor`. Treat `next_cursor` as opaque and pass it back as `cursor` only when `has_next_page` is true. Use `sinceTime` and `untilTime` to bound a poller window. Zero affordable results return `402 insufficient_credits`. ## Build a mentions triage job Use these checkpoints when a support inbox, lead queue, campaign report, or agent workflow needs bounded mention pages with resumable cursor state. Use a username when the handle is stable, or store a numeric user ID for repeat jobs and warehouse joins. Pass `sinceTime` and `untilTime` when a poller, support queue, or campaign report needs a closed mention window. Store author fields, reply context, `conversation_id`, engagement counts, `tweet_url`, and `media_urls` for triage or scoring. Store `page_cursor`, `next_cursor`, and `has_next_page` before requesting another mentions page. ```json theme={null} { "mentions_job_id": "brand-mentions-q2", "mentions_route": "GET /api/v1/x/users/{id}/mentions", "mentioned_user_id_or_username": "username", "since_time": "1777392000", "until_time": "1777478400", "cursor_param": "cursor", "page_cursor": "", "next_cursor": "DAACCgACGRElMJcAAA", "has_next_page": true, "saved_export_tool": "mentions" } ``` | Mention queue column | Response source | Triage use | | --------------------- | --------------------------- | --------------------------------------------------- | | `mentioned_x_user_id` | Resolved target profile ID | Keep every mention tied to the intended account. | | `tweet_id` | `tweets[].id` | De-duplicate mentions and open one source tweet. | | `tweet_url` | `tweets[].url` | Send reviewers to the original X post. | | `text` | `tweets[].text` | Apply support, lead, campaign, or moderation rules. | | `author_id` | `tweets[].author.id` | Keep a stable mention-author key. | | `author_username` | `tweets[].author.username` | Display the current author handle. | | `author_followers` | `tweets[].author.followers` | Add audience context to priority rules. | | `author_verified` | `tweets[].author.verified` | Preserve the observed verification state. | | `created_at` | `tweets[].createdAt` | Enforce the intended support or campaign window. | | `conversation_id` | `tweets[].conversationId` | Group mention tweets from one conversation. | | `in_reply_to_id` | `tweets[].inReplyToId` | Join reply mentions to their immediate parent. | | `media_urls` | `tweets[].media[].mediaUrl` | Preserve image and video context for review. | | Mention rule | Concrete condition | Queue action | | --------------------------- | -------------------------------------------------- | --------------------------------------------------------------- | | Direct support request | Approved support phrase in `text` | Create a support-review item. | | Campaign mention | Campaign phrase within `sinceTime` and `untilTime` | Add the tweet to the campaign report. | | High-reach author | Approved `author.followers` threshold | Raise the review priority without changing the author identity. | | Reply in an existing thread | Non-empty `inReplyToId` or `conversationId` | Attach the existing conversation context. | | Media evidence | Non-empty `media` | Preserve image or video URLs with the triage record. | | No approved rule | No explicit rule matches | Store the mention without inventing a category. | ## Which timeline endpoint? * Use `GET /api/v1/x/users/{id}/mentions` for one user's mentions timeline. * Use `GET /api/v1/x/users/{id}/tweets` for one user's profile timeline. * 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. ## Path parameters X username or numeric user ID. Use a username such as `username` when the profile handle is known, or a numeric ID such as `44196397` when you store stable user IDs. ## Query parameters Pagination cursor for the mentions timeline. Omit it for the first page, then pass the `next_cursor` value from the previous response to fetch the next page. Tweets per page. Range: `1-100`. Defaults to `20`. Unix timestamp in seconds. Only return mentions after this time when a poller, support inbox, or campaign monitor needs a bounded window. Unix timestamp in seconds. Only return mentions before this time. Pair with `sinceTime` for closed reporting windows. ### Tweet result filters These optional filters apply to `tweets[]` returned by this route. They keep the same mentions target and filter rows after each page is fetched, so selective filters can return fewer rows than an unfiltered page. Filter to tweets authored by this username. The `@` prefix is optional. Filter to replies directed to this username. Filter to tweets that mention this username. Only include tweets with this language code, such as `en`, `tr`, or `es`. Filter to tweets created on or after this date or timestamp. Filter to tweets created before this date or timestamp. A `YYYY-MM-DD` value includes the whole day before the boundary. Filter by attached media or links. Values: `images`, `videos`, `gifs`, `media`, `links`, `none`. Only include tweets meeting this minimum like count. Only include tweets meeting this minimum repost count. Minimum reply count. Minimum quote count. Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Tweets without a count pass this filter. Maximum Tweet repost count. Tweets without a count pass this filter. Maximum Tweet reply count. Tweets without a count pass this filter. Maximum Tweet quote count. Tweets without a count pass this filter. When `true`, only return Tweets from Blue-verified authors. Match the Tweet card name. Match the source application. Exclude Tweets from this source application. Match latitude, longitude, and radius in X search syntax. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match this place name. Set the radius for the `near` filter. Match Tweets inside this recent time window. When `true`, only return native reposts. When `true`, enable X safe-search filtering. When `true`, only return news results. When `true`, only return tweets from verified authors. Set `include`, `exclude`, or `only` for reply tweets. Set `include`, `exclude`, or `only` for reposts. Set `include`, `exclude`, or `only` for quote tweets. Exact text that must appear in the tweet. Words or quoted phrases to exclude from returned tweets. Use commas or whitespace between values. Words or quoted phrases where at least 1 term must appear. Use commas or whitespace between values. Only include tweets matching these hashtags. Use commas or whitespace between values. The `#` prefix is optional. Only include tweets matching these cashtags. Use commas or whitespace between values. The `$` prefix is optional. URL substring or domain that must appear in tweet URL entities. Filter to tweets in this conversation thread. Only include replies to this tweet ID. Filter to quote tweets of this tweet ID. Filter to retweets of this tweet ID. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of tweets mentioning the user. **Tweet object fields:** Tweet ID. Tweet text. Tweet type. Omitted if unavailable. ISO 8601 creation timestamp. Whether this is a Note Tweet. Omitted if unavailable. Like count. Omitted if unavailable. Retweet count. Omitted if unavailable. Reply count. Omitted if unavailable. Quote tweet count. Omitted if unavailable. View count. Omitted if unavailable. Bookmark count. Omitted if unavailable. Permalink URL on X. Omitted if unavailable. Tweet language code. Omitted if unavailable. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. User ID being replied to. Omitted if unavailable. Username being replied to. Omitted if unavailable. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities. Omitted if unavailable. Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author X username. Author display name. Follower count. Omitted if unavailable. Whether the author is verified. Omitted if unavailable. Profile picture URL. Omitted if unavailable. Media attachments. Omitted when the tweet has no media. **Media object fields:** Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Cursor for the next page. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "Hey @user check this out!", "createdAt": "2026-03-27T10:00:00.000Z", "likeCount": 5, "author": { "id": "987654321", "username": "customer", "name": "Customer", "followers": 4200, "verified": false, "profilePicture": "https://pbs.twimg.com/profile_images/customer/photo.jpg" } } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid user ID ```json theme={null} { "error": "invalid_user_id", "message": "User not found or invalid user ID. Check the username or ID." } ``` ### 404 User not found ```json theme={null} { "error": "user_not_found", "message": "X user not found. Check the username." } ``` ### 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 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Get user timeline](/api-reference/x/user-tweets) · [User media](/api-reference/x/user-media) · [User likes](/api-reference/x/user-likes) # Twitter Replies Scraper & Profile Timeline Source: https://docs.xquik.com/api-reference/x/user-replies GET /x/users/{id}/replies Retrieve one user's X With Replies timeline with cursor pagination, parent tweet context, author fields, engagement metrics, media, and export handoffs ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "Maximum coverage is busy. Retry shortly." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
* 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)
## Choose Replies By Default Use this route when replies must appear by default. It can include parent context for each reply row. Use the standard user timeline when original posts are the primary target. 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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) 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. 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`. 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. Finished, expired, superseded, or identity-mismatched cursors return `410 coverage_cursor_gone`. The response omits `Retry-After`. Restart without a cursor. Deduplicate restarted results by `id`. Malformed cursors return `400 invalid_coverage_cursor`. Restart without them. Get user replies timeline is the dedicated With Replies endpoint for one public X profile. Use it when replies must be included by default instead of adding `includeReplies=true` to `GET /api/v1/x/users/{id}/tweets`. ```bash cURL theme={null} # Username With Replies timeline curl https://xquik.com/api/v1/x/users/elonmusk/replies \ -H "x-api-key: xq_your_api_key_here" | jq # Numeric user ID With Replies timeline curl https://xquik.com/api/v1/x/users/44196397/replies \ -H "x-api-key: xq_your_api_key_here" | jq # Include parent tweet context for reply rows curl -G https://xquik.com/api/v1/x/users/elonmusk/replies \ --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/replies \ --data-urlencode "cursor=abc123" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const userIdOrUsername = "elonmusk"; let pageCursor = ""; for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) { const params = new URLSearchParams(); if (pageCursor !== "") params.set("cursor", pageCursor); params.set("includeParentTweet", "true"); const response = await fetch( `https://xquik.com/api/v1/x/users/${userIdOrUsername}/replies?${params}`, { headers: { "x-api-key": "xq_your_api_key_here" } }, ); const page = await response.json(); if (!response.ok) throw new Error(JSON.stringify(page)); const replyRows = 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, in_reply_to_username: tweet.inReplyToUsername ?? 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, 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 replyRows) process.stdout.write(`${JSON.stringify(row)}\n`); if (!page.has_next_page || page.next_cursor === "") break; pageCursor = page.next_cursor; } ``` ```python Python theme={null} import json import requests user_id_or_username = "44196397" page_cursor = "" for page_index in range(3): params = {"includeParentTweet": "true"} if page_cursor: params["cursor"] = page_cursor response = requests.get( f"https://xquik.com/api/v1/x/users/{user_id_or_username}/replies", 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"]: reply_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"), "in_reply_to_username": tweet.get("inReplyToUsername"), "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"), "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(reply_row, separators=(",", ":"))) if not page["has_next_page"] or not page["next_cursor"]: break page_cursor = page["next_cursor"] ``` ## User replies handoff Use `GET /x/users/{id}/replies` when a support queue, community workflow, research job, or agent needs a profile's With Replies timeline. This endpoint accepts either a username or numeric user ID, includes replies by default, and returns one JSON page at a time. Store `source_user_id_or_username`, `tweet_id`, `text`, author fields, `created_at`, reply context, `conversation_id`, engagement counts, `media_urls`, `page_cursor`, `has_next_page`, and `next_cursor`. Treat `next_cursor` as opaque and pass it back as `cursor` only when `has_next_page` is true. ## Build a With Replies sync Use these checkpoints when a timeline sync needs reply rows, parent context, and resumable cursor state. Call `GET /x/users/{id}/replies` when every page should include replies by default. Set `includeParentTweet=true` when reply rows need the parent tweet for triage, moderation, or conversation joins. Use [`Get user timeline`](/api-reference/x/user-tweets) when replies are optional. Use this route when replies are required. Persist `page_cursor`, `next_cursor`, and `has_next_page` before requesting another page. ```json theme={null} { "timeline_job_id": "profile-replies-q2", "timeline_route": "GET /api/v1/x/users/{id}/replies", "user_id_or_username": "elonmusk", "include_parent_tweet": true, "cursor_param": "cursor", "page_cursor": "", "next_cursor": "DAADDAABCgABF...", "has_next_page": true, "fallback_route": "GET /api/v1/x/users/{id}/tweets?includeReplies=true" } ``` ## Which timeline endpoint? * Use `GET /api/v1/x/users/{id}/replies` for one user's With Replies timeline. Replies are included by default. * Use `GET /api/v1/x/users/{id}/tweets` for one user's profile timeline when replies are optional or should be excluded by default. * Add `includeParentTweet=true` when reply rows need parent tweet context. * Use `GET /api/v1/x/users/{id}/media` when every returned row should contain profile media. * Use `GET /api/v1/x/tweets/{id}/replies` for replies under one specific tweet. ## Build a profile reply archive Use this route to collect replies authored by one profile. Preserve the source profile ID with every reply. Also preserve the replied-to tweet ID when available. Useful reply columns include: * Reply tweet ID, text, and creation time. * Author username and numeric user ID. * Parent or conversation identifiers. * Like, reply, repost, quote, and view counts. * Media URLs, cursor, and collection time. Use reply rows for support review, conversation research, or approved archiving. Do not merge them into original posts without a clear reply flag. Deduplicate by reply tweet ID. Save every page before advancing its cursor. New replies can shift the first page between runs. Use tweet replies to inspect replies under one specific tweet. Use user tweets for a profile timeline. Those routes begin from different targets. | Profile reply column | Response source | Archive rule | | -------------------- | --------------------------- | ----------------------------------------------------------- | | `source_x_user_id` | Resolved profile ID | Keep the authored-reply archive tied to one stable profile. | | `reply_id` | `tweets[].id` | Use as the stable reply upsert and deduplication key. | | `text` | `tweets[].text` | Preserve the reply body returned by the API. | | `created_at` | `tweets[].createdAt` | Order replies by creation time, not cursor position. | | `in_reply_to_id` | `tweets[].inReplyToId` | Join each reply to its immediate parent tweet. | | `conversation_id` | `tweets[].conversationId` | Group reply rows from the same X conversation. | | `parent_tweet` | Included parent object | Preserve parent context when `includeParentTweet=true`. | | `like_count` | `tweets[].likeCount` | Rank authored replies by observed likes. | | `reply_count` | `tweets[].replyCount` | Identify replies that started deeper discussion. | | `media_urls` | `tweets[].media[].mediaUrl` | Preserve images and videos attached to the reply. | | `page_cursor` | Request `cursor` | Prove which With Replies page produced the row. | | `collected_at` | Integration timestamp | Distinguish repeated reply archive snapshots. | | Reply collection requirement | Route and parameters | Use | | -------------------------------------- | ------------------------------------------ | -------------------------------------------------------- | | Every authored reply | `/x/users/{id}/replies` | Collect one profile's With Replies timeline. | | Parent tweet context | Add `includeParentTweet=true` | Review the source tweet beside each authored reply. | | Optional replies in a profile timeline | `/x/users/{id}/tweets?includeReplies=true` | Combine original posts and replies in one profile feed. | | Replies under one tweet | `/x/tweets/{id}/replies` | Collect every returned reply to a specific source tweet. | | Media-only profile posts | `/x/users/{id}/media` | Collect profile tweets that contain media. | ## Path parameters X username or numeric user ID. Use a username such as `elonmusk` when the profile handle is known, or a numeric ID such as `44196397` when you store stable user IDs. ## Query parameters Pagination cursor for the With Replies timeline. Omit it for the first page, then pass the `next_cursor` value from the previous response to fetch the next page. Automatic pages accept `1` through `300`. Unprefixed legacy cursors accept `1` through `100`. Source availability, filters, or credits can return fewer. Include parent tweet context for returned replies. Defaults to `false`; set it to `true` when replies need conversation context. ### Tweet result filters These optional filters apply to `tweets[]` returned by this route. They keep the same user target and filter rows after each page is fetched, so selective filters can return fewer rows than an unfiltered page. Filter to tweets authored by this username. The `@` prefix is optional. Filter to replies directed to this username. Filter to tweets that mention this username. Only include tweets with this language code, such as `en`, `tr`, or `es`. Filter to tweets created on or after this date or timestamp. Filter to tweets created before this date or timestamp. A `YYYY-MM-DD` value includes the whole day before the boundary. Filter by attached media or links. Values: `images`, `videos`, `gifs`, `media`, `links`, `none`. Only include tweets meeting this minimum like count. Only include tweets meeting this minimum repost count. Minimum reply count. Minimum quote count. Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Tweets without a count pass this filter. Maximum Tweet repost count. Tweets without a count pass this filter. Maximum Tweet reply count. Tweets without a count pass this filter. Maximum Tweet quote count. Tweets without a count pass this filter. When `true`, only return Tweets from Blue-verified authors. Match the Tweet card name. Match the source application. Exclude Tweets from this source application. Match latitude, longitude, and radius in X search syntax. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match this place name. Set the radius for the `near` filter. Match Tweets inside this recent time window. When `true`, only return native reposts. When `true`, enable X safe-search filtering. When `true`, only return news results. When `true`, only return tweets from verified authors. Set `include`, `exclude`, or `only` for reply tweets. Set `include`, `exclude`, or `only` for reposts. Set `include`, `exclude`, or `only` for quote tweets. Exact text that must appear in the tweet. Words or quoted phrases to exclude from returned tweets. Use commas or whitespace between values. Words or quoted phrases where at least 1 term must appear. Use commas or whitespace between values. Only include tweets matching these hashtags. Use commas or whitespace between values. The `#` prefix is optional. Only include tweets matching these cashtags. Use commas or whitespace between values. The `$` prefix is optional. URL substring or domain that must appear in tweet URL entities. Filter to tweets in this conversation thread. Only include replies to this tweet ID. Filter to quote tweets of this tweet ID. Filter to retweets of this tweet ID. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of tweets from the user's With Replies timeline. **Tweet object fields:** Tweet ID. Tweet text content. Tweet type. Omitted if unavailable. ISO 8601 creation timestamp. Omitted if unavailable. Whether this is a Note Tweet (long-form post). Omitted if unavailable. Like count. Omitted if unavailable. Retweet count. Omitted if unavailable. Reply count. Omitted if unavailable. Quote tweet count. Omitted if unavailable. View count. Omitted if unavailable. Bookmark count. Omitted if unavailable. Permalink URL on X. Omitted if unavailable. Tweet language code. Omitted if unavailable. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. User ID being replied to. Omitted if not a reply. Username being replied to. Omitted if not a reply. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities (URLs, hashtags, mentions). Omitted if unavailable. Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author X username. Author display name. Follower count. Omitted if unavailable. Whether the author is verified. Omitted if unavailable. Profile picture URL. Omitted if unavailable. Media attachments. Omitted if unavailable. **Media item fields:** Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Opaque cursor for the next page. Empty string when no more results. ```json theme={null} { "tweets": [ { "id": "1893456789012345678", "text": "Reply tweet content", "createdAt": "2026-02-24T10:00:00.000Z", "isReply": true, "inReplyToId": "1893456000000000000", "conversationId": "1893456000000000000", "likeCount": 500, "retweetCount": 120, "replyCount": 45, "viewCount": 25000, "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": "User not found or invalid user ID. Check the username or ID." } ``` 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." } ``` ### 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Get user timeline](/api-reference/x/user-tweets) · [Tweet replies](/api-reference/x/tweet-replies) · [User media](/api-reference/x/user-media) # Search User Tweets, Profile Timeline & Cursors Source: https://docs.xquik.com/api-reference/x/user-tweets GET /x/users/{id}/tweets Search one Twitter or X profile's tweets with full text, replies, reposts, likes, quotes, views, attached media, and cursor pagination. See API fields. ```json theme={null} { "tweets": [ { "id": "1234567890", "text": "Just launched our new feature!", "retweetCount": 5, "replyCount": 3, "likeCount": 42 } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "Maximum coverage is busy. Retry shortly." } ```
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
* 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)
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`. **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit 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. 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`. 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. Finished, expired, superseded, or identity-mismatched cursors return `410 coverage_cursor_gone`. The response omits `Retry-After`. Restart without a cursor. Deduplicate restarted results by `id`. Malformed cursors return `400 invalid_coverage_cursor`. Restart without them. Search user tweets returns the public profile timeline for one Twitter or X account. Use it for "user tweets," "profile timeline," or "X user timeline" searches. Keep the canonical route as `GET /api/v1/x/users/{id}/tweets`. ```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"] ``` ## 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 either a username or numeric user ID and 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 so a worker can resume from the last saved `next_cursor`. For high-volume timeline pulls, de-duplicate tweets by `id`, continue through empty filtered pages when the cursor advances, and stop with a partial-result status when `next_cursor` is missing or repeats. ## Build a profile timeline job Use these checkpoints when a timeline sync needs to switch between plain profile posts, replies, media-only rows, and resumable page pulls. Omit `includeReplies` to fetch the profile timeline without replies. Set `includeReplies=true` and `includeParentTweet=true` when support, community, or research rows need the parent tweet context. Use a `mediaType` filter for filtered timeline rows, or switch to [`User media`](/api-reference/x/user-media) when every row should contain media. Store `page_cursor`, `next_cursor`, and `has_next_page` before requesting another page. ```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, and 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/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. Do not treat row order as durable identity. New tweets can change the first page. Use tweet IDs for updates and deduplication. | 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 | Preserve 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` | Preserve 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` | Preserve image, video, or animated GIF URLs. | | `page_cursor` | Request `cursor` | Prove which profile timeline page produced the row. | | `collected_at` | Integration timestamp | Distinguish repeated profile timeline snapshots. | | 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. | | 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 X username or numeric user ID. Use a username such as `elonmusk` when the profile handle is known, or a numeric ID such as `44196397` when you store stable user IDs. ## Query parameters 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. Automatic pages accept `1` through `300`. Unprefixed legacy cursors accept `1` through `100`. Source availability, filters, or credits can return fewer. Include reply tweets in the profile timeline. Defaults to `false`, which returns original profile posts without replies. Include parent tweet context for returned replies. Defaults to `false`; set it to `true` when replies need conversation context. ### Tweet result filters These optional filters apply to `tweets[]` returned by this route. They keep the same user target and filter rows after each page is fetched, so selective filters can return fewer rows than an unfiltered page. Filter to tweets authored by this username. The `@` prefix is optional. Filter to replies directed to this username. Filter to tweets that mention this username. Only include tweets with this language code, such as `en`, `tr`, or `es`. Filter to tweets created on or after this date or timestamp. Filter to tweets created before this date or timestamp. A `YYYY-MM-DD` value includes the whole day before the boundary. Filter by attached media or links. Values: `images`, `videos`, `gifs`, `media`, `links`, `none`. Only include tweets meeting this minimum like count. Only include tweets meeting this minimum repost count. Minimum reply count. Minimum quote count. Minimum Tweet view count. Minimum Tweet bookmark count. Maximum Tweet like count. Tweets without a count pass this filter. Maximum Tweet repost count. Tweets without a count pass this filter. Maximum Tweet reply count. Tweets without a count pass this filter. Maximum Tweet quote count. Tweets without a count pass this filter. When `true`, only return Tweets from Blue-verified authors. Match the Tweet card name. Match the source application. Exclude Tweets from this source application. Match latitude, longitude, and radius in X search syntax. Return Tweets newer than this Tweet ID. Return Tweets older than this Tweet ID. Match this place name. Set the radius for the `near` filter. Match Tweets inside this recent time window. When `true`, only return native reposts. When `true`, enable X safe-search filtering. When `true`, only return news results. When `true`, only return tweets from verified authors. Set `include`, `exclude`, or `only` for reply tweets. Set `include`, `exclude`, or `only` for reposts. Set `include`, `exclude`, or `only` for quote tweets. Exact text that must appear in the tweet. Words or quoted phrases to exclude from returned tweets. Use commas or whitespace between values. Words or quoted phrases where at least 1 term must appear. Use commas or whitespace between values. Only include tweets matching these hashtags. Use commas or whitespace between values. The `#` prefix is optional. Only include tweets matching these cashtags. Use commas or whitespace between values. The `$` prefix is optional. URL substring or domain that must appear in tweet URL entities. Filter to tweets in this conversation thread. Only include replies to this tweet ID. Filter to quote tweets of this tweet ID. Filter to retweets of this tweet ID. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of tweets by the user. **Tweet object fields:** Tweet ID. Tweet text content. Tweet type. Omitted if unavailable. ISO 8601 creation timestamp. Omitted if unavailable. Whether this is a Note Tweet (long-form post). Omitted if unavailable. Like count. Omitted if unavailable. Retweet count. Omitted if unavailable. Reply count. Omitted if unavailable. Quote tweet count. Omitted if unavailable. View count. Omitted if unavailable. Bookmark count. Omitted if unavailable. Permalink URL on X. Omitted if unavailable. Tweet language code. Omitted if unavailable. Whether the tweet is a reply. Omitted if unavailable. Tweet ID being replied to. Omitted if not a reply. User ID being replied to. Omitted if not a reply. Username being replied to. Omitted if not a reply. Conversation thread ID. Omitted if unavailable. Client used to post the tweet. Omitted if unavailable. Start and end offsets for rendered tweet text. Omitted if unavailable. Whether replies are limited. Omitted if unavailable. Whether this tweet quotes another tweet. Omitted if unavailable. Parsed entities (URLs, hashtags, mentions). Omitted if unavailable. Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable. Tweet author profile. Omitted if unavailable. **Author object fields:** Author user ID. Author X username. Author display name. Follower count. Omitted if unavailable. Whether the author is verified. Omitted if unavailable. Profile picture URL. Omitted if unavailable. Media attachments. Omitted if unavailable. **Media item fields:** Direct media URL. Available video renditions with bitrate, content type, and URL. Omitted for images. Media type. Shortened URL from the tweet text. Embedded quoted tweet. Omitted if not a quote tweet. Original retweeted tweet. Omitted if not a retweet. Whether more results are available. Opaque cursor for the next page. Empty string when no more results. ```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": "UserDeclared", "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": "User not found or invalid user ID. Check the username or ID." } ``` 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." } ``` ### 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [User replies timeline](/api-reference/x/user-replies) · [User media](/api-reference/x/user-media) · [User likes](/api-reference/x/user-likes) · [User mentions timeline](/api-reference/x/user-mentions) # Twitter Verified Followers API & Profile Export Source: https://docs.xquik.com/api-reference/x/verified-followers GET /x/users/{id}/verified-followers Retrieve verified X followers by username or numeric user ID with cursor pagination for CRM, scoring, enrichment, and agent workflows. See API fields. ```json theme={null} { "users": [ { "id": "9876543210", "username": "elonmusk", "name": "Elon Musk" } ], "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA" } ``` ```json theme={null} { "error": "invalid_input", "message": "Invalid input. Check the request body." } ``` ```json theme={null} { "error": "unauthenticated", "message": "Authentication required." } ``` ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` ```json theme={null} { "error": "not_found", "message": "Resource not found." } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "invalid_input" } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 60 } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "X data source temporarily unavailable. Try again later." } ``` ```json theme={null} { "error": "x_api_unavailable", "message": "Maximum coverage is busy. Retry shortly." } ```
For the complete documentation index, see llms.txt.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Retrieve verified profiles that follow one X account. Store stable user IDs, handles, verification types, counts, and pagination cursors. 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`. **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Supports [guest paid reads](/guides/guest-wallets) Get verified followers returns verified profiles that follow one X account by username or numeric user ID. It is also useful as a verified followers API, X verified followers API, or Twitter verified followers API. The canonical endpoint remains `GET /api/v1/x/users/{id}/verified-followers`. 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. 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`. 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. Finished, expired, superseded, or identity-mismatched cursors return `410 coverage_cursor_gone`. The response omits `Retry-After`. Restart without a cursor. Deduplicate restarted results by `id`. Malformed cursors return `400 invalid_coverage_cursor`. Restart without them. ```bash Username theme={null} curl "https://xquik.com/api/v1/x/users/username/verified-followers" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```bash Numeric user ID theme={null} curl "https://xquik.com/api/v1/x/users/44196397/verified-followers" \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const userId = "44196397"; const response = await fetch(`https://xquik.com/api/v1/x/users/${userId}/verified-followers`, { headers: { "x-api-key": "xq_your_api_key_here" }, }); const data = await response.json(); const verifiedRows = data.users.map((user) => ({ source_user_id: userId, x_user_id: user.id, username: user.username, display_name: user.name, verified_type: user.verifiedType ?? "standard", follower_count: user.followers ?? null, following_count: user.following ?? null, profile_image_url: user.profilePicture ?? null, })); const nextCursor = data.has_next_page ? data.next_cursor : null; const checkpoint = { source_user_id: userId, next_cursor: nextCursor }; for (const row of verifiedRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } process.stdout.write(`${JSON.stringify(checkpoint)}\n`); ``` ```python Python theme={null} import json import requests user_id = "44196397" response = requests.get( f"https://xquik.com/api/v1/x/users/{user_id}/verified-followers", headers={"x-api-key": "xq_your_api_key_here"}, ) data = response.json() verified_rows = [ { "source_user_id": user_id, "x_user_id": user["id"], "username": user["username"], "display_name": user["name"], "verified_type": user.get("verifiedType", "standard"), "follower_count": user.get("followers"), "following_count": user.get("following"), "profile_image_url": user.get("profilePicture"), } for user in data["users"] ] next_cursor = data["next_cursor"] if data["has_next_page"] else None checkpoint = {"source_user_id": user_id, "next_cursor": next_cursor} for row in verified_rows: print(json.dumps(row)) print(json.dumps(checkpoint)) ``` The Node.js and Python snippets shape durable verified follower rows instead of printing the full response page. Persist `verifiedRows` or `verified_rows` with the checkpoint so a worker can resume pagination with `next_cursor` without duplicating already imported profiles. ## Direct verified followers handoff Use `GET /x/users/{id}/verified-followers` when a CRM, warehouse, scoring, enrichment, or agent workflow needs one JSON page of verified followers now. The endpoint accepts either a username or numeric user ID and returns verified follower profile rows with cursor fields. Use [`verified_follower_explorer`](/api-reference/extractions/create) when you need an estimated job, saved extraction, or CSV/JSON/XLSX file export. Store `users[]` as the verified follower profile rows returned on this page. Store `users[].id` as `x_user_id` for CRM, warehouse, scoring, and agent dedupe. Store `users[].username` and `users[].name` for handles, labels, enrichment, and review queues. Store `users[].verified` and `verifiedType` to segment standard, business, and government accounts. Store `users[].followers`, `users[].following`, and `statusesCount` for scoring and prioritization. Store `users[].description`, `location`, `url`, `profilePicture`, and `coverPicture` when returned. Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor` only when `has_next_page` is true. Direct verified followers cost 1 credit per user returned. Low credit balances can return fewer users than a full page; zero affordable results return `402 insufficient_credits`. ## Review verified followers separately Use this route when the verification filter is part of the question. Keep the source account ID with every returned profile. Export fields such as user ID, username, profile name, biography, location, verification state, and follower counts. Save the cursor and collection time with each page. Verified follower rows can support account research, partner review, or audience segmentation. Verification does not prove relevance or endorsement. Apply another review step before outreach or ranking. Compare snapshots by user ID. Do not use username changes as new follower events. Keep the complete followers endpoint separate when analysis needs the entire audience. Paginate until no next cursor remains. Deduplicate resumed exports by source account and follower ID. ## Build a Verified Follower Directory Start with one source account ID or username. Resolve and store its stable user ID. Keep that source ID on every verified follower row. Store follower user ID, username, profile name, biography, location, and profile image when returned. Keep `verified` and `verifiedType` as separate fields. Add follower count, following count, and collection time. Use verification type for a documented segment. Do not translate it into authority, relevance, identity quality, or endorsement. Add those judgments only through a reviewed downstream process. Use stable follower IDs for CRM upserts. Treat usernames, names, biographies, images, verification, and counts as replaceable profile attributes. Keep rejected import rows for review. Choose direct cursor pages for current application reads. Choose `verified_follower_explorer` for estimates, saved jobs, and downloadable CSV, JSON, or XLSX files. Record which path created every directory snapshot. ## Validate Verified Follower Export Completeness Save every page before advancing its opaque cursor. Request another page only when `has_next_page` is true. Pass `next_cursor` back unchanged. Deduplicate resumed pages by source account ID and follower user ID. Keep the newest complete profile fields. Preserve the earliest collection evidence when an audit needs it. Record page count, unique row count, first cursor, last cursor, and completion time. Mark result caps, low credits, failed pages, and interrupted downloads. Never call those runs complete. Compare the exported row count with the saved extraction result when using a job. Investigate duplicate user IDs, rejected rows, and parsing errors. Check incomplete downloads before loading a warehouse. Refresh verification before time-sensitive segmentation. Verification can change after collection. Keep the collection time beside every exported row. ## Path parameters User ID (numeric) or username. ## Query parameters Pass `next_cursor` back unchanged. New Xquik cursors resume automatic coverage. Existing unprefixed cursors keep legacy behavior. 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. Legacy cursor alias. Use `cursor`; when both are present, `cursor` wins. Automatic pages accept `20` through `300`. Standard pages accept `20` through `200`. The default is `200`. Sources can return fewer profiles. With `mode=coverage`, set a one-shot cap from `1` through `10000`. Otherwise, this is a legacy page size alias. `pageSize` wins. ## Which verified follower endpoint? Use `GET /x/users/{id}/verified-followers` for verified accounts that follow one profile. Use [`GET /x/users/{id}/followers`](/api-reference/x/followers) when you need all followers, not only verified accounts. Use [`GET /x/users/{id}/following`](/api-reference/x/following) for accounts the profile follows. Use [`verified_follower_explorer`](/api-reference/extractions/create) for a saved verified follower extraction with CSV, JSON, or XLSX download handoff. ### User result filters These filters apply before billing. Selective filters can return fewer rows. Require this minimum follower count. Filtering happens before billing. Allow this maximum follower count. Missing counts pass this filter. Require this minimum following count. Allow this maximum following count. Missing counts pass this filter. Require this minimum post count. Allow this maximum post count. Missing counts pass this filter. Require this minimum account age in days. When `true`, only return verified profiles. Match the exact verification type. When `true`, require a profile website. When `true`, require a profile location. Require every comma-separated or line-separated bio term. Require this text in the profile location. Require this text in the username. ## Headers Full account key. Sessions and OAuth also work. `Bearer xq_your_guest_key_here` for `paid_reads`. ## Response ### 200 OK Array of verified follower profiles. **User object fields:** X user ID. X username. Display name. Profile bio. Omitted if empty. Follower count. Following count. Whether the user is verified. Always true for this endpoint. Verification type (e.g. `Business`, `Government`). Omitted for standard blue check. Profile picture URL. Profile location. Omitted if empty. ISO 8601 account creation timestamp. Total number of tweets posted. Omitted if unavailable. Cover/banner image URL. Omitted if unavailable. Total number of media tweets posted. Omitted if unavailable. Website URL from profile. Omitted if empty. Total number of tweets liked. Omitted if unavailable. Whether the user has custom timelines. Omitted if unavailable. Whether the user is an X translator. Omitted if unavailable. Country codes where the account is withheld. Omitted if empty. Whether the account is flagged as possibly sensitive. Omitted if unavailable. IDs of pinned tweets. Omitted if none. Whether the account is marked as automated. Omitted if unavailable. Username of the account operator if automated. Omitted if not automated. Whether the account is unavailable. Omitted if available. Reason the account is unavailable. Omitted if available. Structured profile bio with entity annotations. Omitted if unavailable. Whether the account has X Premium verification. Omitted if unavailable. Normalized verification status. Omitted if unavailable. Profile banner URL. Omitted if unavailable. Whether the account protects its posts. Omitted if unavailable. Role within the requested community context. Omitted outside community results. Whether more results are available. Cursor for the next page. ```json theme={null} { "users": [ { "id": "987654321", "username": "username", "name": "Xquik", "followers": 10000, "verified": true, "verifiedType": "Business" } ], "has_next_page": true, "next_cursor": "DAACCgACGE..." } ``` ### 400 Invalid user ID ```json theme={null} { "error": "invalid_user_id", "message": "User not found or invalid user ID. Check the username or ID." } ``` ### 404 User not found ```json theme={null} { "error": "user_not_found", "message": "X user not found. Check the username." } ``` ### 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 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 } ``` Your tier rate limit was exceeded. 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. **Related:** [Create extraction](/api-reference/extractions/create) with `verified_follower_explorer` for saved verified follower jobs, [Export extraction](/api-reference/extractions/export) for CSV, JSON, or XLSX downloads, [Get followers](/api-reference/x/followers), [Get following](/api-reference/x/following), and [Get followers you know](/api-reference/x/followers-you-know). # Xquik Changelog: Twitter API & Scraper Updates Source: https://docs.xquik.com/changelog Track Xquik REST, MCP, SDK, tweet search, follower export, monitor, webhook, extraction, account action, and contract changes. Includes exact examples.
For the complete documentation index, see llms.txt.
Follow Xquik documentation updates for tweet search, follower exports, monitors, webhooks, and SDKs. Track API keys and signed webhook deliveries. Search tweets and review user profile updates. Use this Xquik changelog to review REST, MCP, authentication, billing, and write changes. **New** * Accountless guest wallets prepay 33 GET routes through a scoped `paid_reads` key * `POST /guest-wallets`, `GET /guest-wallets/status`, and `POST /guest-wallets/topups` support hosted checkout after confirmation without an account * Active guest keys expose the same 33-route read-only catalog through API MCP * API MCP v2.6.0 supports MCP `2026-07-28` through `server/discover` * Modern discovery and tool catalogs include private cache hints * `POST /compose` now returns 7 source-specific `radarRecommendations` with endpoints, use cases, and drafting guidance * Reddit Radar items now expose available post text, destination URLs, media, public engagement signals, estimated vote counts, and post state * `GET /x/account-connection-attempts/{id}` reports a tracked connection as pending, successful, failed, or waiting for an email code **Changed** * Modern MCP calls are request-scoped and require no initialization session * Stateless 2025-era MCP clients remain compatible at the same endpoint * Field guidance now documents REST naming exceptions and MCP normalization * Export guidance now separates file formats from downstream column selection * Account connection email-code challenges now return immediately as a resumable `202 requires_email_code` response * `POST /x/accounts` can return a durable `202 pending` response with `Location` and `Retry-After`; follow that attempt instead of resending credentials * Account connection failures now expose the exact cooldown through `retryAfterMs` and `Retry-After`; retry controls show a live countdown * Direct MPP now covers 7 fixed-price `charge` operations; result-sized reads use guest or full account credits * Follow checks and article reads cost USD 0.00075 per direct MPP call * Every response after an accepted MPP payment includes `Payment-Receipt`, including non-2xx responses * The 26 non-MPP anonymous paid reads return `401` with `WWW-Authenticate: Bearer` and a guest wallet action * The 7 direct MPP reads return `402` with `WWW-Authenticate: Payment` and the same guest action * Reddit Radar now refreshes rich server-rendered items every 30 minutes and no longer creates reduced RSS fallback items * Startup growth Radar items now add founder, company, acquisition, margin, and revenue-efficiency fields when available * A successful Compose score now points to `GET /x/accounts` and `POST /x/tweets`, while retaining the one-click `intentUrl` **Removed** * Obsolete public `proxy_country` and `loginCountry` account fields. Custom, dedicated, and fixed per-account proxy configuration is not supported. **New** * `POST /x/tweets` - added `media` field (1-4 image URLs); `text` is now optional when media is present * `POST /x/accounts/{id}/reauth` - added `email` and `proxy_country` parameters * `POST /x/media` - now accepts `image/webp` and `image/avif` (allowed: AVIF, GIF, JPEG, PNG, WebP, MP4) * `POST /x/accounts` and `POST /x/accounts/{id}/reauth` responses now include a `health` field (enum: `healthy`, `locked`, `needsReauth`, `recovering`, `suspended`, `temporaryIssue`); same field added to `GET /x/accounts` list and detail responses * `POST /x/accounts` and `POST /x/accounts/{id}/reauth` may include an optional `loginCountry` field (ISO-3166-1 alpha-2) when the login session country differs from the selected `proxy_country` * New error code `login_cooldown` (429) for `POST /x/accounts` and `POST /x/accounts/{id}/reauth`, with `reason` and `retryAfterMs` in the body and a `Retry-After` header * All 17 write endpoints (under `/x/tweets`, `/x/users`, `/x/dm`, `/x/profile`, `/x/media`, `/x/communities`) now document standardised error responses: `403 account_needs_reauth`, `403 account_restricted`, `422 write_rejected`, `429 rate_limited_by_x`, `503 transient_error` * `GET /radar` items now expose 15 metadata fields per source (was 8): `mrr`, `growthPercent`, `last30Days`, `total` (required), `customers`, `activeSubscriptions`, `onSale`, plus optionals `askingPrice`, `country`, `growthMrrPercent`, `multiple`, `paymentProvider`, `rank`, `category`, `xHandle` * Expanded Machine Payments Protocol (MPP) pay-per-use to 31 endpoints (tweets, communities, lists, user data) * New framework guides: LangChain, Pydantic AI, CrewAI, Mastra, Google ADK, Microsoft Agent Framework **Changed** * Billing migrated to credits-only model (usage-quota removed) * `GET /account` response: `creditInfo` replaces `currentPeriod` * `POST /extractions/estimate` response: `creditsRequired` / `creditsAvailable` replace `usagePercent` / `projectedPercent` * Real-time WebSocket monitor sync retired in favour of an async poller. No public API contract change; underlying delivery model changed. * `POST /x/tweets/{id}/like` and `POST /x/tweets/{id}/retweet` are now end-to-end idempotent. Calling either on an already-acted-on tweet returns 200 success rather than the prior silent rejection that produced a 500 plus an account cooldown. **Removed** * Event types `follower.gained` and `follower.lost` are no longer emitted. Valid event types: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet` (plus `webhook.test`). * Error code `shadow_account` (422) removed from `POST /monitors` (shadow-check subsystem deleted) * Error code `stream_registration_failed` (502) removed from `POST /monitors` (monitor activation is now asynchronous) * Telegram bot endpoints and integrations pipeline endpoints removed **New** * MPP expanded from 16 to 31 eligible endpoints * Webhook delivery signing now uses HMAC-SHA256 with `X-Xquik-Signature` header **Changed** * OAuth 2.1 discovery metadata now served from `/.well-known/oauth-authorization-server` * Rate limit responses include `Retry-After` header # AG2 Twitter API Search Guide for Python Agents Source: https://docs.xquik.com/guides/ag2 Give AG2 agents typed tweet search through Xquik with XquikSearchToolkit, runtime Variables, cursor pagination, and delegated multi-agent research workflows.
For the complete documentation index, see llms.txt.
[AG2](https://github.com/ag2ai/ag2) is an open-source Python framework for multi-agent systems. AG2 1.0.0 and later ship `XquikSearchToolkit` in `ag2.extensions.tools.search`, so Xquik tweet search reaches AG2 agents without a community adapter. AG2 documents the toolkit in its [Xquik Tweet Search reference](https://docs.ag2.ai/docs/user-guide/extensions/tools/search/xquik/). Preserve every tweet ID and cursor the API returns. ## Why Use AG2 With the Xquik Twitter API? AG2 binds search parameters when a tool is constructed, not when the model calls it. The agent chooses the query. You choose the window, the ordering, and the result limit. | Boundary | AG2 control | Benefit | | ----------------- | ------------------------- | ---------------------------------------------------------------------- | | Tool construction | `XquikSearchToolkit(...)` | Fix `query_type`, `limit`, and time windows outside model control | | Model surface | Single `query` argument | The model cannot widen the search window or result limit | | Runtime scope | `Variable` | Resolve per-user or per-tenant values at execution time | | Delegation | `Agent.as_tool()` | Keep the searcher's tool-call history out of the coordinator's context | | Transport | `base_url`, `timeout` | Point at a proxy or tighten deadlines per deployment | This suits research, monitoring, and reporting agents. Use direct REST for deterministic jobs that need no model decisions. ## AG2 Twitter API Prerequisites * Python 3.10 or later * An [Xquik API key](/x-api-quickstart) beginning with `xq_` * An LLM provider key supported by AG2 Public X reads need no X Developer credentials. Authenticate with Xquik. ## Install AG2 ```bash theme={null} python -m pip install "ag2>=1.0.0" ``` `XquikSearchToolkit` calls the Xquik REST API over `httpx`, which AG2 already depends on. No extra package is required. Install your model provider extra as well. ```bash theme={null} python -m pip install "ag2[anthropic]>=1.0.0" ``` Store secrets outside source control. ```bash .env theme={null} XQUIK_API_KEY=xq_YOUR_KEY_HERE ANTHROPIC_API_KEY=YOUR_ANTHROPIC_KEY ``` ```text .gitignore theme={null} .env ``` ## Register the Xquik Tweet Search Tool Passing the toolkit to an agent registers one tool, `xquik_tweet_search`. It takes a single `query` argument holding an X search string. ```python theme={null} import asyncio import os from ag2 import Agent from ag2.config import AnthropicConfig from ag2.extensions.tools.search import XquikSearchToolkit from dotenv import load_dotenv load_dotenv() config = AnthropicConfig(model="claude-sonnet-4-6") agent = Agent( "x-researcher", prompt=( "Search X for evidence before answering. " "Quote tweet text verbatim and keep every tweet ID you receive." ), config=config, tools=[XquikSearchToolkit(api_key=os.environ["XQUIK_API_KEY"])], ) async def main() -> None: reply = await agent.ask("What are developers saying about the Xquik API this week?") print(reply.body) asyncio.run(main()) ``` `api_key` is required. The toolkit raises `ValueError` when it is empty, so a missing key fails at construction rather than on the first search. ## Bind the Search Window Outside Model Control Search defaults belong on the constructor. The model then cannot widen the window or raise the result limit. ```python theme={null} toolkit = XquikSearchToolkit( api_key=os.environ["XQUIK_API_KEY"], query_type="Latest", # "Latest" or "Top" limit=50, since_time="2026-01-01T00:00:00Z", until_time="2026-02-01T00:00:00Z", ) ``` | Parameter | Type | Purpose | | ------------ | --------------------- | ----------------------------------------------- | | `query_type` | `"Latest"` \| `"Top"` | Result ordering | | `limit` | `int` | Upper bound on returned results | | `since_time` | `str` | Start of the time window | | `until_time` | `str` | End of the time window | | `cursor` | `str` | Resume from a prior page | | `base_url` | `str` | Defaults to `https://xquik.com` | | `timeout` | `float` | Request deadline in seconds, defaults to `60.0` | The same parameters are available on the `search()` factory method when you want several differently scoped tools from one toolkit. ```python theme={null} toolkit = XquikSearchToolkit(api_key=os.environ["XQUIK_API_KEY"]) latest = toolkit.search( query_type="Latest", limit=50, name="search_latest_posts", description="Search the newest public X posts.", ) top = toolkit.search( query_type="Top", limit=20, name="search_top_posts", description="Search the highest-engagement public X posts.", ) agent = Agent("analyst", config=config, tools=[latest, top]) ``` ## Read the Structured Result `xquik_tweet_search` returns a typed response rather than a raw payload. | Field | Type | Meaning | | --------------- | ------------ | -------------------------------------------- | | `query` | `str` | The query that was searched | | `tweets` | `list[dict]` | Tweet records exactly as returned by the API | | `has_next_page` | `bool` | Whether another page exists | | `next_cursor` | `str` | Cursor for the next page, empty when absent | Tweet records pass through unmodified, so every tweet ID, profile ID, and timestamp survives the hop into the agent. Feed `next_cursor` back through the `cursor` parameter to continue pagination. ## Resolve Values at Runtime With Variables Every runtime parameter accepts an AG2 `Variable`. AG2 resolves it from the run context when the tool executes, so one tool instance serves many users or tenants. ```python theme={null} from ag2.annotations import Variable toolkit = XquikSearchToolkit( api_key=os.environ["XQUIK_API_KEY"], since_time=Variable("window_start"), until_time=Variable("window_end"), limit=Variable("page_size"), ) ``` ## Delegate Search in a Multi-Agent Team `Agent.as_tool()` exposes an agent as a tool for another agent. Each delegated task runs on its own stream with its own history. The coordinator receives the delegate's final answer, not its internal tool-call history. ```python theme={null} searcher = Agent( "searcher", prompt="Search X and return tweet text with IDs. Do not summarise.", config=config, tools=[XquikSearchToolkit(api_key=os.environ["XQUIK_API_KEY"], query_type="Latest", limit=50)], ) analyst = Agent( "analyst", prompt="Turn tweet records into a factual brief. Keep every tweet ID.", config=config, ) coordinator = Agent( "coordinator", prompt="Delegate the search, then pass the tweets to the analyst.", config=config, tools=[ searcher.as_tool(description="Search public X posts and return raw tweet records."), analyst.as_tool(description="Analyse tweet records. Pass them in the context parameter."), ], ) reply = await coordinator.ask("Brief me on this week's discussion of X API pricing.") print(reply.body) ``` ## Handle Failures The tool raises on non-success HTTP status codes through `httpx`. Read [Error Handling](/guides/error-handling) for status semantics and [Rate Limits](/guides/rate-limits) for retry guidance. Wrap the toolkit with AG2 tool middleware when you need retries, approval gates, or audit logging around every search. ## Related Guides * [AG2 Xquik Tweet Search reference](https://docs.ag2.ai/docs/user-guide/extensions/tools/search/xquik/) # Platform Architecture | X API Workflow Guide Source: https://docs.xquik.com/guides/architecture Learn how Xquik searches tweets, exports followers, monitors accounts, signs webhooks, isolates accounts, and enforces API rate limits. Follow exact steps.
For the complete documentation index, see llms.txt.
Xquik is a hosted service for tweet search, follower exports, profile lookup, monitors, webhooks, and X account actions.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
* 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)
You interact through the REST API, MCP server, SDKs, or dashboard. You do not deploy Xquik infrastructure or configure X API credentials. ## Architecture Overview ```text theme={null} ┌───────────────────────────────────────────────┐ │ Clients │ │ REST API · MCP · SDKs · Dashboard · CLI │ └──────────────────────┬────────────────────────┘ │ HTTPS ┌──────────────────────▼────────────────────────┐ │ Public Xquik Interfaces │ │ Authentication · Rate Limits · Usage Gates │ ├───────────────────────────────────────────────┤ │ Read Service · Write Service · Webhooks │ └──────────────────────┬────────────────────────┘ │ ┌──────────────────────▼────────────────────────┐ │ Documented Responses & Events │ └───────────────────────────────────────────────┘ ``` ### Components 128 documented operations at `https://xquik.com/api/v1/*` for apps, backends, scripts, and fine-grained pagination. 2 tools, `explore` and `xquik`, at `https://xquik.com/mcp` for ChatGPT, Claude, Cursor, and agent workflows. Manage API keys, connected X accounts, monitors, extractions, draws, webhooks, media, billing, and support. Track accounts or keywords, store events, and deliver HMAC-signed webhook payloads with retry history. Run stored jobs for followers, replies, quotes, retweeters, favoriters, search, articles, and giveaway draws. Post tweets and replies, upload media, send DMs, follow, like, retweet, update profiles, and poll write status. See [integration workflows](/guides/workflows) for end-to-end code examples using these components. ## Security Model ### Authentication Xquik REST uses **API key authentication**. API MCP accepts **OAuth 2.1** or an Xquik API key when the client supports secure request headers. ChatGPT custom apps require OAuth and cannot present custom API keys. Send `x-api-key` on every REST API request. MCP clients can authenticate with the same Xquik API key. Keys start with `xq_` followed by 64 hex characters. The dashboard shows the full key only once. Xquik returns the full key only during creation. Store it securely. Revoked or inactive keys stop authenticating immediately and return `401`. Account audit views show API-key activity. MCP also supports [OAuth 2.1 with S256 PKCE](/oauth/overview) for clients that require delegated authorization. Create and revoke keys through the authenticated dashboard. API keys are shown once at creation. Store them securely. There is no way to retrieve a key after creation. ### Data Isolation Every API key is scoped to a single user account. There is no cross-user access. Account and keyword monitors are scoped to the creating user account. Stored events resolve through account or keyword monitor ownership before returning data. Webhook endpoints, signing configuration, and delivery logs belong to one user. Extraction jobs, result pages, and exports belong to the user that created the job. Giveaway draws, entries, and winner lists belong to the user that created the draw. API-key listing, creation, and revocation filter by the authenticated user ID. Attempting to access another user's resources returns `404 Not Found` (not `403`), preventing enumeration attacks. ### Authorization Xquik uses a flat permission model: no roles, no RBAC, no team workspaces. * **One user, one account**: Each account has full access to all its own resources * **API key scope**: A valid account API key can perform API-key-authorized operations for that account * **API key management**: Listing, creating, and revoking keys require a same-origin dashboard session. API keys and OAuth bearer tokens cannot manage keys * **Credit gates**: Creating extractions, draws, active monitors, media downloads, and X lookups require enough available credits. All webhook operations are free. Reading and managing stored jobs, monitors, and events is free. Active monitors cost 21 credits per hour. ## Rate Limits Rate limits are enforced per user account using a **fixed-window counter** algorithm. Each tier has an independent counter. Read counters reset every 1 second; write and delete counters reset every 60 seconds. `GET`, `HEAD`, and `OPTIONS` share a standard user limit of 300 requests per 1 second. `POST`, `PUT`, and `PATCH` share a standard user limit of 120 requests per 60 seconds. `DELETE` requests are limited to 60 requests per 60 seconds. Throttled reads return `Retry-After: 1`; throttled writes and deletes return `Retry-After: 60`. When the limit is reached, requests return `429 Too Many Requests` with a `Retry-After` header. Read throttles return `Retry-After: 1`; write and delete throttles return `Retry-After: 60`. See the [Rate Limits](/guides/rate-limits) guide for detailed explanations, backoff strategies, and client-side rate limiter code examples. ## Usage & Billing Starter, Pro, and Business plans run from USD 20 to USD 199 per month and include monthly credits. Monitor slots are unlimited. Active monitors check every 1 second and cost 21 credits per active monitor-hour. Top up from USD 10. Credits are priced at USD 0.00015 each. See [Billing & Usage](/guides/billing#credit-top-ups). ### What Counts as Usage Paid X reads, media downloads, trends, extraction estimates, extraction creation, monitor creation, active monitor hours, and draw execution can consume credits. Tweet search, user and follower lookup, article lookup, media download, trends, draw creation, and publish actions require enough available credits. List, read, update, delete, export, test, and delivery-history paths stay free for draws, extractions, monitors, events, and webhooks. Compose, cached styles, drafts, radar, account, API keys, X accounts, support, credit balance, and credit top-up endpoints are free. See [Billing & Usage](/guides/billing) for credit costs and billing. ## Monitoring Architecture Xquik checks active account and keyword monitors every second. ```text theme={null} X signals │ ▼ Monitor processing ──▶ Stored events │ ▼ Signed webhooks ──▶ Customer HTTPS endpoint ``` Account monitors emit `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet`. Xquik sends an HMAC-SHA256 signed HTTPS `POST` to each active webhook endpoint. Verify `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce`. Failed deliveries retry up to 10 attempts with exponential backoff: base 1 second, multiplier 2x, max 60 seconds. `410 Gone` exhausts immediately. Webhook receivers should return `2xx` within 10 seconds. Slow or non-`2xx` responses are recorded as failed attempts. Events usually appear within seconds to minutes, depending on X stream timing and webhook receiver availability. ## Platform Limitations Bookmarks and bookmark folders require a connected X account. Use [bookmarks](/api-reference/x/bookmarks) and [bookmark folders](/api-reference/x/bookmark-folders). Extraction exports are capped at 100,000 rows. PDF exports are capped at 10,000 rows. Supported formats: CSV, JSON, MD, MD Document, PDF, TXT, and XLSX. Webhook deliveries try up to 10 attempts. `410 Gone` exhausts immediately; other failures retry until delivered or exhausted. Monitor slots are unlimited. Active monitors check every 1 second and cost 21 credits per active monitor-hour. ## Next Steps Get up and running with your first API call. API key format, header requirements, and dual auth. Fixed-window limits, backoff strategies, and code examples. Pricing, credit allowances, and billing. # Xquik API Pricing, Credits & Billing | Billing API Source: https://docs.xquik.com/guides/billing Compare tweet search and follower export costs, then manage Xquik subscriptions, guest wallets, credits, top-ups, carry-over, and MPP pay-per-use. See examples.
For the complete documentation index, see llms.txt.
Compare Twitter API pricing and X API pricing with Xquik. Read the [Twitter API alternative and pricing guide](/alternatives/x-api). This page covers Xquik API pricing for tweet search and follower exports. It covers replies, profiles, timelines, monitors, webhooks, and write actions.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
Account workflows spend shared credits. Plans grant monthly credits that carry over. Guest wallets prepay 33 GET routes. MPP pays per request on 7 operations. ## Subscription USD 20/month. Includes 140,000 monthly credits (USD 0.00014/credit). Prototyping and low-volume integrations. Monitor slots are unlimited. USD 99/month. Includes 770,000 monthly credits (USD 0.00013/credit). Production workloads and growing teams. Monitor slots are unlimited. USD 199/month. Includes 1,670,000 monthly credits (USD 0.00012/credit). High-volume automation and enterprise use. Monitor slots are unlimited. Higher tiers give you more credits at a lower per-credit cost. PAYG credits cost USD 0.00015 each. All tiers support unlimited monitor slots. Active monitors cost 21 credits per hour and check every second. Stored events and webhook delivery are included. Subscribe from the [dashboard billing page](https://dashboard.xquik.com/en/account?tab=subscription). Plans activate immediately, renew monthly, and grant credits. Metered calls need enough available credits, not an active plan. Guest wallets and MPP provide accountless reads. ### Pick a billing path by job Use these cards when you need to estimate a real X API task before you run it: Use [`GET /x/tweets/search`](/api-reference/x/search-tweets) for a page of tweets or [extractions](/guides/extraction-workflow) for exportable jobs. Cost: 1 credit per tweet returned or extracted. Use [Follower extraction](/guides/extraction-workflow) for CSV/JSON/XLSX exports or [`GET /x/users/{id}/followers`](/api-reference/x/followers) for paginated API reads. Cost: 1 credit per follower returned. Use [Create tweet](/api-reference/x-write/create-tweet). Send public media URLs in `media` when posting media. Cost: 30 credits text-only, plus 2 credits per started MB across attached media. Use [Upload media](/api-reference/x-write/upload-media) when a DM needs an uploaded `media_id`. Cost: 10 credits per media upload call. Use [account and keyword monitors](/api-reference/monitors/create) for tweet alerts and signed webhooks. Cost: 21 credits per active monitor-hour, with a 500-credit daily estimate. Inspect `payment_options`. Ask the user to choose and confirm. Account, guest, and anonymous callers receive different payment choices. ### Monitor pricing Account monitors and keyword monitors use the same active billing rate: Account monitor slots are unlimited. Active account monitors bill only while enabled. Keyword monitor slots are unlimited. Active keyword monitors use the same hourly rate. Each active monitor costs 21 credits per monitor-hour. Active monitors check every 1 second. Webhook and event deliveries are included in active monitor billing. Creating or reactivating an account monitor requires at least 22 available credits: 1 credit for the username lookup plus the 21-credit first active monitor-hour. Creating or reactivating a keyword monitor also requires at least 22 available credits and then bills active monitor hours at 21 credits per hour while enabled. New active monitors are due for billing immediately. The create response includes `nextBillingAt`; after a successful hourly charge, the next billing time advances by 1 hour. If hourly billing cannot charge enough credits, the monitor may pause until credits are available. Use [`GET /account`](/api-reference/account/get) to inspect current monitor billing. `monitorsUsed`, `monitorBilling.activeHourlyBurn`, and `monitorBilling.activeDailyEstimate` include active account monitors and active keyword monitors. ### Plan monitor credits before you monitor tweets Use `GET /account` before creating more tweet monitors or tweet alerts. Each active account monitor or keyword monitor adds `21` credits to `monitorBilling.activeHourlyBurn` and `500` credits to `monitorBilling.activeDailyEstimate`. Hourly burn: 21 credits/hour. Daily estimate: 500 credits/day. Keep at least 22 credits before creating or reactivating one more monitor. Hourly burn: 105 credits/hour. Daily estimate: 2,500 credits/day. Keep at least 22 credits before creating or reactivating one more monitor. Hourly burn: 210 credits/hour. Daily estimate: 5,000 credits/day. Keep at least 22 credits before creating or reactivating one more monitor. The daily estimate is the value returned by the account API for capacity planning. It is intentionally rounded for alerting and top-up thresholds. If `creditInfo.balance` is below the next hourly burn, top up before enabling more monitors. ## Monthly credits & carry-over Every paid subscription invoice adds the monthly credit grant to your account balance. Subscription credits, top-up credits, and automatic top-up credits stay in that balance until you spend them. * All metered operations deduct from a **single shared pool** * No separate buckets per operation type * Unused subscription credits **carry over** to the next billing period * API calls are also subject to [rate limits](/guides/rate-limits) (separate from credit usage) * When the shared balance reaches 0, metered calls return `402 Payment Required` (see [error handling](/guides/error-handling#billing--credit-errors-402) for recovery patterns) * Reading and managing stored monitor records, stored events, webhooks, and account endpoints are free. Active monitors are billed hourly. ```json 402 response body theme={null} { "error": "insufficient_credits", "message": "Insufficient credits. Top up or subscribe to continue." } ``` > **Warning:** Metered calls are rejected when available credits cannot cover the operation. [Top up credits](/api-reference/credits/topup) or subscribe to continue. ## Per-operation costs Each metered operation deducts credits from your shared pool. Per-credit cost depends on your plan - from USD 0.00012 (Business) to USD 0.00015 (PAYG). | Endpoint | Unit | Credits | | --------------------------------------------------------- | ------------------------------------ | ------- | | [Bookmark folders](/api-reference/x/bookmark-folders) | per call | 1 | | [Bookmarks](/api-reference/x/bookmarks) | per result | 1 | | Community join/leave | per call | 10 | | [Create tweet](/api-reference/x-write/create-tweet) | text-only call | 30 | | Create tweet attached media | per started MB across all files | 2 | | [Delete tweet](/api-reference/x-write/delete-tweet) | per call | 10 | | [DM history](/api-reference/x/dm-history) | per result | 1 | | [Download media](/api-reference/x/download-media) | per fresh tweet processed with media | 1 | | [Favoriters](/api-reference/x/favoriters) | per result | 1 | | [Follow](/api-reference/x-write/follow) | per call | 10 | | [Follow check](/api-reference/x/check-follower) | per call | 5 | | [Followers you know](/api-reference/x/followers-you-know) | per result | 1 | | [Get article](/api-reference/x/get-article) | per call | 5 | | [Get tweet](/api-reference/x/get-tweet) | per call | 1 | | [Get user](/api-reference/x/twitter-profile-lookup) | per call | 1 | | [Like](/api-reference/x-write/like) | per call | 10 | | Active monitor | per monitor-hour | 21 | | [Notifications](/api-reference/x/notifications) | per result | 1 | | [Retweet](/api-reference/x-write/retweet) | per call | 10 | | [Remove follower](/api-reference/x-write/remove-follower) | per call | 10 | | [Search tweets](/api-reference/x/search-tweets) | per tweet returned | 1 | | [Send DM](/api-reference/x-write/send-dm) | per call | 10 | | [Timeline](/api-reference/x/timeline) | per result | 1 | | [Trends](/api-reference/x/trends) | per call | 3 | | [Unfollow](/api-reference/x-write/unfollow) | per call | 10 | | [Unlike](/api-reference/x-write/unlike) | per call | 10 | | [Unretweet](/api-reference/x-write/unretweet) | per call | 10 | | [Upload media](/api-reference/x-write/upload-media) | per call | 10 | | [User likes](/api-reference/x/user-likes) | per result | 1 | | [User media](/api-reference/x/user-media) | per result | 1 | | [User replies timeline](/api-reference/x/user-replies) | per tweet returned | 1 | | [User timeline](/api-reference/x/user-tweets) | per tweet returned | 1 | | [Verified followers](/api-reference/x/verified-followers) | per result | 1 | ### Extractions & draws Extractions consume 1 or 5 credits per result depending on the extraction type: 1 credit per result. Includes tweets, replies, quotes, mentions, posts, likes, media, and search exports. 1 credit per result. Includes followers, following, favoriters, retweeters, community members, people search, list members, list followers, and verified followers. 5 credits per result. Applies to article extractions. Draws charge for the source tweet lookup, reply search, optional retweeter profile checks when `mustRetweet` is true, and optional follow checks for unique authors when `mustFollowUsername` is set. The draw first checks that the minimum cost is affordable. Remaining credits cap how many replies and retweeters can be inspected before filters and winner selection run. If the final computed cost cannot be deducted, the API returns `402 insufficient_credits` and does not persist a draw result. ### Credit-affordable result pages Paid X read endpoints that charge per returned result treat `limit`, `pageSize`, `count`, and multi-ID request sizes as upper bounds. If the remaining credit balance cannot cover the full requested page, the endpoint can return fewer results than requested. If zero paid results are affordable, it returns `402 insufficient_credits`. ### Free operations The following do not consume credits: stored monitor management, stored event reads, webhook operations, extraction and draw reads or exports, cost estimates, composition, styles, drafts, support, API keys, X account management, credit top-ups, account status, and [Radar](/api-reference/radar/list). Active monitors cost 21 credits per hour. ## Credit top-ups Ask the user to confirm before creating account checkout or charging a saved payment method. * Use the [top-up endpoint](/api-reference/credits/topup) to create a checkout redirect after confirmation * Use the [top-up status endpoint](/api-reference/credits/topup-status) to poll checkout completion * Use the [quick top-up endpoint](/api-reference/credits/quick-topup) to charge your saved payment method * Configure automatic top-up from the dashboard. `GET /credits` and `GET /account` return the enabled status, dollar amount, and trigger threshold. * Top-up credits cost USD 0.00015 each and are added to your balance immediately * Top-up credits do not expire and carry over between billing periods > **Example:** Starter tier (USD 20, 140K credits). A USD 10 top-up adds 66,666 credits at USD 0.00015 per credit, rounded down to whole credits. ### Recover from 402 An account `402` creates no checkout. Keep the failed request, inspect `payment_options`, and get explicit confirmation before billing. 1. Check the current balance: ```bash theme={null} curl -s https://xquik.com/api/v1/credits \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```json theme={null} { "balance": "450", "lifetime_purchased": "1000", "lifetime_used": "550", "auto_topup_enabled": false, "auto_topup_amount_dollars": 10, "auto_topup_threshold": "50000" } ``` 2. After confirmation, create a checkout top-up when no saved payment method is available: ```bash theme={null} curl -s -X POST https://xquik.com/api/v1/credits/topup \ -H "x-api-key: xq_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"dollars": 10}' | jq ``` ```json theme={null} { "url": "https://xquik.com/api/v1/credits/topup/redirect?session_id=checkout_session_id", "redirect_url": "https://xquik.com/api/v1/credits/topup/redirect?session_id=checkout_session_id" } ``` 3. After confirmation, use quick top-up only when the account already has a saved payment method: ```bash theme={null} curl -s -X POST https://xquik.com/api/v1/credits/quick-topup \ -H "x-api-key: xq_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"dollars": 25}' | jq ``` ```json theme={null} { "outcome": "charged", "balance": "167116", "credits": "166666" } ``` A USD 25 quick top-up adds 166,666 credits at USD 0.00015 per credit, rounded down to whole credits. Only the `charged` quick top-up outcome grants credits. If quick top-up returns `no_payment_method`, create a checkout top-up instead. If it returns `requires_action`, complete the payment confirmation flow before retrying the metered API call. ## Accountless guest wallets Guest wallets prepay the 33 eligible GET routes without an account. After the user confirms $10-$250 USD, `POST /api/v1/guest-wallets` creates a one-use hosted checkout and returns a `paid_reads` key. Creation does not charge. The key stays inactive until payment is verified. A guest `402` offers only `POST /api/v1/guest-wallets/topups`. See the [guest wallet guide](/guides/guest-wallets). ## Pay-per-use (MPP) Seven fixed-price read operations accept direct [MPP](/mpp/machine-payments-protocol) payments. No subscription is required. Media downloads require full account authentication because they create account-tied gallery links. ### MPP per-call pricing Use [MPP overview](/mpp/machine-payments-protocol#eligible-endpoints) for the complete 7-operation list. Direct MPP uses fixed `charge` pricing: `GET /x/tweets/{id}`, `GET /x/users/{id}`, and `GET /x/communities/{id}/info` cost USD 0.00015 per call. `GET /x/followers/check` and `GET /x/articles/{tweetId}` cost USD 0.00075 per call. Trend lookups use flat charge intent pricing: `GET /trends` and `GET /x/trends`. Every direct MPP operation advertises one fixed `charge` offer per request. ## Checking usage Call `GET /api/v1/account` to see your credit balance, lifetime usage, and monitor billing: ```bash theme={null} curl -s https://xquik.com/api/v1/account \ -H "x-api-key: xq_your_api_key_here" | jq ``` **Response:** ```json theme={null} { "plan": "active", "monitorsAllowed": 9007199254740991, "monitorsUsed": 1, "monitorBilling": { "activeDailyEstimate": "500", "activeHourlyBurn": "21", "creditsPerActiveMonitorDay": "500", "creditsPerActiveMonitorHour": "21", "eventsIncluded": true, "instantCheckIntervalSeconds": 1, "unlimitedSlots": true }, "creditInfo": { "balance": "77000", "lifetimePurchased": "140000", "lifetimeUsed": "63000", "autoTopupEnabled": false, "autoTopupAmountDollars": 10, "autoTopupThreshold": "50000" } } ``` The `creditInfo.balance` field shows how many credits remain. The automatic top-up fields show whether top-up is enabled, the charge amount, and the balance threshold. At 0, metered calls are rejected until you [top up credits](/api-reference/credits/topup). > **Tip:** Poll the account endpoint periodically to track usage. Build alerts when `creditInfo.balance` drops below 20% of your plan's monthly credit grant to avoid hitting the limit unexpectedly. ## Cancel renewal & request a refund Open [subscription settings](https://dashboard.xquik.com/en/account?tab=subscription), select **Manage Plan**, and cancel in the hosted billing portal. Cancellation stops future renewals. Access continues through the paid period. Unused credits remain usable after the plan ends. Cancellation does not issue a refund. Charges are non-refundable unless law or Xquik states otherwise. Review the [Terms](https://xquik.com/en/terms) or contact [support](mailto:support@xquik.com). ## FAQ ### What happens when I run out of credits? Account and guest credit failures return `402` with credential-specific payment choices. Anonymous non-MPP paid reads return `401` with a Bearer challenge and guest wallet action. The 7 direct MPP reads return `402` with a Payment challenge and the same action. No error creates checkout or charges a payment method. ### Do unused credits carry over? Yes. Subscription credits and top-up credits stay in the shared balance until you spend them. ### How do I check my credit balance? Call `GET /api/v1/account` - the `creditInfo.balance` field shows remaining credits. ## Next steps * [Get Account](/api-reference/account/get): Full account endpoint reference with response schema. * [API Overview](/api-reference/overview): Base URL, authentication, rate limits, and conventions. # Monitor Twitter Accounts & Keywords with Webhooks Source: https://docs.xquik.com/guides/brand-monitoring-workflow Monitor Twitter accounts, keywords, mentions, hashtags, products, and campaigns every second. Deliver signed webhook alerts with replayable stored events.
For the complete documentation index, see llms.txt.
Use this Twitter monitoring API for support, marketing, research, or agent pipelines. It handles fresh X activity. It combines 2 Twitter monitoring tools: account monitors and keyword monitors. Choose the right Twitter monitoring tool for one profile or one search query. Monitor Twitter mentions, deliver matching events to a signed Twitter webhook, and replay stored events. Use one keyword monitor to monitor Twitter keywords and campaign phrases. Use one account monitor to monitor Twitter account activity from a known profile. Route mentions of your brand into support and community management queues. Measure engagement metrics from unique tweets and authors. Review each match before marketing teams create content or replies.
* 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)
## Build a Real Time Monitoring Workflow for Twitter Xquik monitors X only. Add another social media platform connector for other networks. When tracking brand mentions, keep each X event separate. Test each tool with the same queries during a free trial. Compare Tweet IDs, author IDs, timestamps, webhook delivery, and replay results. Use real time Twitter monitoring for campaign and support queries. Keep each keyword and hashtag in a separate monitor. Test every Boolean search before activation. Store historical data from tweet-search backfills separately. Keep live monitor events in their own stream. Analyze data from matched tweets by Tweet ID, author ID, timestamp, and query. Classify each preserved tweet after its monitor event arrives. Create a temporary product launch monitor for its name and branded hashtag. Connect verified events to an AI powered tweet triage service when needed. Keep automated classification downstream from Xquik's original tweet payload. ## Pick the Monitor Path Use `POST /api/v1/monitors` for one account. Track its posts, replies, reposts, quotes, or profile field changes. Use `POST /api/v1/monitors/keywords` for a saved search. Track a brand, handle, hashtag, product, campaign, or Boolean query. Use `POST /api/v1/webhooks` to deliver monitor events with `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce`. Use `GET /api/v1/events` to replay account or keyword monitor events. Recover from receiver downtime, queue errors, or warehouse load failures. ## Answer Common Twitter Monitoring Questions ### What Is the Best API to Track Twitter Keyword Mentions? Choose an API that stores the exact query and stable event IDs. Test one query for a brand, handle, hashtag, product, or campaign. The Twitter keyword monitor checks for matching tweets every 1 second. Review each Tweet ID, author ID, text, timestamp, media, and matched keyword. Sign every webhook event before sending it to the receiver. Queue each event before acknowledging delivery. Replay stored events after receiver downtime. Compare missed tweet counts for the same query in each tool. Use duplicate alert counts during the final tool comparison. ### How Do I Monitor a Keyword on Twitter in Real Time? Create a keyword monitor with `POST /api/v1/monitors/keywords`. Use one Boolean query for each product, campaign, hashtag, or support topic. Store its monitor ID and exact query. Test the keyword against recent tweets first. Inspect reply, author, language, and media fields. Subscribe a webhook to required event types. Verify its signature, timestamp, and nonce. Save each event before returning `2xx`. Replay stored events after receiver downtime. Pause inactive monitors after the assigned campaign concludes. ### Track Keywords Twitter API Use `POST /api/v1/monitors/keywords` for continuous keyword checks. Separate competitor names and handles from product, support, and campaign queries. Save the monitor ID, query, owner, event types, and enabled state. Test matching tweets before enabling the monitor. Store each Tweet ID and author ID. Deduplicate webhook deliveries by event ID. Put rate-limit or receiver failures into the normal retry path. Never create a changed monitor during those retries. Pause the keyword monitor after its campaign ends. ### Twitter Mention Tracking Tool Xquik provides a Twitter mention tracking tool for webhook review queues. Marketing teams can route matching tweets to their own review queues. This routing helps protect brand reputation. Use precise queries to track Twitter mentions in relevant conversations for review. Review each tweet, reply, author, and media attachment. Store the matched keyword, monitor ID, event ID, and delivery ID. Connect Xquik events to broader social listening tools for other networks. Review sentiment and author intent downstream. Compare reply routing too. ### Twitter Keyword Monitor A Twitter keyword monitor watches one stored search expression. Use one exact brand name, @handle, hashtag, campaign phrase, or product name per monitor. Run a tweet search before approving the keyword. Review tweets, replies, reposts, author profiles, languages, and media. Save the approved query beside its monitor ID. Subscribe the verified webhook only to events its receiver can process. Queue received events durably. Deduplicate every delivery by event ID. Pause the monitor after its campaign or investigation closes. Store matched Tweet IDs. ### What Is the Best Way to Monitor a Twitter Account Programmatically? Create an account monitor with `POST /api/v1/monitors`. Supply one username and required event types. Store its stable X user ID and monitor ID. The Twitter account monitor API follows new tweets, replies, reposts, and quotes. It also tracks documented profile changes. Deliver each account event to a signed Twitter webhook receiver. Queue the payload before returning `2xx`. Deduplicate deliveries by event ID. Replay stored events after receiver downtime. Reuse the same monitor during uncertain retries. Pause inactive profiles. ### Monitor Twitter Mentions Track an @handle, brand spelling, misspelling, product, or campaign hashtag. A handle query finds explicit mentions. Brand keywords find untagged references. Campaign queries isolate time-bounded work. Test every query with tweet search. Review tweets, replies, authors, profiles, and media before continuous checks. Route each matching event to the responsible queue. Store Tweet ID, author ID, timestamp, query, monitor ID, and event ID. Review false matches before broadening a keyword. Replay stored events after webhook downtime. ### Twitter Webhook Alerts Create one signed webhook for the monitor events your application handles. Verify `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce`. Reject any request with a stale timestamp or reused nonce. Queue the payload, then return `2xx`. Use `streamEventId` for event identity. Use `deliveryId` for each receiver retry or initial delivery. Store monitor ID, keyword, event type, Tweet ID, and author ID. Record the queue result without logging secrets. Replay stored events after webhook or queue failures. Deduplicate every retried delivery before alerting reviewers during outages. ### Twitter Account Monitor API Use the account monitor API for one known profile. Select the needed tweet, reply, repost, quote, or profile change events. Store the stable X user ID beside the username. Also store monitor ID, owner, event types, webhook destination, and active state. Deduplicate each delivery by event ID. Keep Tweet IDs for post events. Pause accounts after they leave the monitored set. Use keyword monitors for terms spanning many profiles. Each account-monitor workflow should track one relevant profile. Review each profile change before updating downstream records. ### How Do I Get Real-Time Twitter Alerts via Webhook? Create a receiver with `POST /api/v1/webhooks`. Subscribe it to matching monitor events. Verify every signature before parsing the event. Queue valid payloads before returning `2xx`. Use `streamEventId` for event identity. Use `deliveryId` for each receiver attempt. These Twitter webhook alerts remain stored for event recovery. They provide instant alerts without making receiver uptime the only recovery path. Record the monitor, event, Tweet, and author IDs. Also record the receive timestamp and durable queue result. ### How Do Businesses Use Twitter Monitoring for Customer Service? Create one keyword monitor for the brand handle and support phrases. Add one account monitor for replies to the owned profile. Route matching tweets and replies to the assigned support queue. Keep the Tweet, author, conversation, query, and event IDs for each alert. Verify the webhook before queueing each alert. Load the conversation thread when a support agent reviews the alert. Record assignment, response decision, and resolution time. Replay stored events after downtime. Never publish an automatic reply without account approval. ### How Do I Monitor Competitor Activity on Twitter? Create separate keyword monitors for each competitor name and handle. Add focused monitors for each product and active campaign phrase during research. Use account monitors when every post from one profile matters. Test each keyword with tweet search before continuous monitoring. Review replies, reposts, authors, media, and recurring false matches. Route each event to the assigned research queue. Store the query, monitor ID, Tweet ID, and event ID for each research alert. Compare unique tweets across equal collection windows. Show paused or incomplete collection windows beside the result totals. Record every query revision. ### How Do I Integrate Twitter Monitoring Into a Dashboard? Send verified webhook events to a durable queue. Transform each event into one dashboard record with a stable event identity. Use `streamEventId` for that identity. Show tweet text, author, query, monitor, event type, and received time. Keep historical search backfills separate from live monitor events. Calculate totals from unique Tweet IDs and author IDs. Report delivery failures separately from conversation volume. Replay missing events before closing a reporting window. Display replies, reposts, profiles, and media beside each tweet. ### What Are Best Practices for Twitter Monitoring in Crisis Management? Prepare focused keyword monitors before an incident. Include exact handles, products, campaign terms, and known misspellings. Assign one incident owner and one verified webhook receiver. Give matching crisis tweets and replies priority during review. Keep Tweet ID, author ID, query, timestamp, and event ID. Review the full conversation before any public response. Never infer urgency from one keyword alone. Replay stored events after outages. Pause temporary monitors after the incident review ends. Record each monitor change. ## Plan a Brand Monitoring Scope ### Assign One Monitoring Goal Teams comparing social media monitoring tools can use Xquik for X-specific monitoring. Assign one owner per monitor. ### Choose One Monitor Type Use an account monitor for one known profile. Use a keyword monitor for text that can appear across many profiles. Keep the stable X user ID or exact query with its monitor ID. ### Assign One Receiver Assign one receiver. Subscribe only to event types it handles. ## Create an Account Monitor Use account monitors for profile events. ```bash theme={null} curl -X POST https://xquik.com/api/v1/monitors \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "username": "username", "eventTypes": ["tweet.new", "tweet.reply", "profile.bio.changed"] }' | jq ``` Store the returned `id`, `username`, `xUserId`, `eventTypes`, `isActive`, `createdAt`, and `nextBillingAt` with the brand workspace. Review the [Create Account Monitor API contract](https://docs.xquik.com/api-reference/monitors/create). ## Create a Keyword Monitor Use a keyword monitor when a search query triggers the workflow. Keep the query under 512 characters. Store its ID with the exact query. ```bash theme={null} curl -X POST https://xquik.com/api/v1/monitors/keywords \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "query": "\"Xquik\" OR @username", "eventTypes": ["tweet.new", "tweet.reply"] }' | jq ``` ## Write Focused Twitter Monitoring Queries Use tweet search to test each query before monitoring starts. A Boolean search should describe one brand, product, campaign, hashtag, or handle. Review tweets, replies, authors, media, and query noise before activation. ## Deliver Events to a Webhook Create one webhook per receiver. Store the returned `secret` once. Verify every request before queueing the event. ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/xquik/brand-monitor", "eventTypes": ["tweet.new", "tweet.reply", "profile.bio.changed"] }' | jq ``` Webhook payloads include `deliveryId`, `streamEventId`, `eventType`, `timestamp`, and `data`. Return `2xx` after verification and durable queueing. ## Route Real Time Alerts to the Right Team Verify signatures, timestamps, and nonces before queueing alerts. Use `streamEventId` as the event identity across webhook endpoints. Use `deliveryId` for each delivery attempt. Process slow work after queueing. ## Replay Stored Events Replay stored events after receiver or queue failures. ```bash theme={null} curl "https://xquik.com/api/v1/events?monitorId=42&eventType=tweet.new&limit=50" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```bash theme={null} curl "https://xquik.com/api/v1/events?keywordMonitorId=21&eventType=tweet.new&limit=50" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Store `hasMore` and `nextCursor`. Continue pagination while `hasMore` remains true by sending `nextCursor` as `cursor` for the next request. ## Add Search Backfill Use search or mention reads for earlier tweets and replies. ```bash theme={null} curl "https://xquik.com/api/v1/x/tweets/search?q=%22Xquik%22%20OR%20%40username&limit=50" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```bash theme={null} curl "https://xquik.com/api/v1/x/users/username/mentions?limit=50" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ## Measure Brand Monitoring Coverage Count engagement metrics from unique tweet IDs and author IDs. Separate account events from keyword events. Deduplicate by `streamEventId`. ### Calculate Share of Voice Xquik does not return a calculated share of voice. Build Twitter analytics downstream from unique tweet IDs and event windows. Let the social media management team approve posts in its publishing workflow. For share of voice, compare one brand's unique-tweet count with every brand's count. ### Analyze Tweet Sentiment Downstream Classify sentiment only after storing the original tweet. Preserve that tweet text beside its classifier version. ### Report Delivery Health Compare stored event IDs with webhook records. Label partial reporting windows. ## Receiver Row ```json theme={null} { "brand_monitor_id": "brand-xquik-q2", "monitor_type": "keyword", "account_monitor_id": null, "keyword_monitor_id": "21", "webhook_id": "15", "delivery_id": "502", "stream_event_id": "9002", "event_type": "tweet.new", "tweet_id": "1893704267862470862", "x_user_id": "987654321", "username": "customer_handle", "query": "\"Xquik\" OR @username", "signature_verified": true, "received_at": "2026-05-24T20:12:00.000Z", "event_replay_route": "GET /api/v1/events?keywordMonitorId=21" } ``` Keep endpoint signing values, raw request bodies, raw signatures, or full headers private. ## Build a Brand Mention Triage Queue Show tweet text, author, time, event type, query, and monitor. Store review state against `streamEventId`. Avoid automatic public replies from a brand monitoring event. Review the conversation and account first. Approve X write actions separately. ## Maintain Account and Keyword Monitors Review each monitor's owner, query, event types, receiver, and `nextBillingAt`. Compare each monitor's event types with webhook subscriptions. Test one signed delivery after changes. Pause unused monitors. ## Cost and Retry Notes Active account and keyword monitors check every 1 second and cost 21 credits per active monitor-hour. Event storage and webhook delivery are included. Stored event listing is free. Search, mention, and other direct read backfills are metered by returned rows. Pause inactive monitors with `PATCH /api/v1/monitors/{id}` or `PATCH /api/v1/monitors/keywords/{id}` and `{ "isActive": false }`. ## Next Steps Track one account's posts, replies, reposts, quotes, and profile field changes. Track a search query for brand, campaign, support, or category terms. Send signed monitor events to a receiver URL. Replay stored monitor events by account monitor, keyword monitor, event type, and cursor. # Twitter Campaign Verification API Workflow Source: https://docs.xquik.com/guides/campaign-verification-workflow Verify giveaway follows, retweets, replies, quotes, winners, and participant exports through one reviewable Twitter campaign workflow. Includes API examples.
For the complete documentation index, see llms.txt.
Use this workflow when a campaign needs one reviewable row per participant. Choose the narrowest check first. Use a draw when winners need selection. Campaign verification separates entry collection, rule checks, winner selection, and publication. Store one campaign ID across every stage. Keep source Tweet IDs, participant user IDs, filters, cursors, timestamps, and verification states. Running a giveaway without these checkpoints makes later review unreliable.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
## Pick the Proof Path Use `GET /api/v1/x/followers/check?source={participant}&target={brand}` to check one participant and one target account. Use `GET /api/v1/x/tweets/{id}/retweeters` to page retweeters. Use `GET /api/v1/x/tweets/{id}/replies` for replies. Use `GET /api/v1/x/tweets/{id}/quotes` for quote posts. Use `POST /api/v1/draws` for winner selection and published filters. Translate every published participation rule into one documented check. Use follower relationships for follow rules. Use retweeter pages for repost rules. Use reply rows for keywords, hashtags, and mentions. Use quote rows when quotes define campaign participation. Store the endpoint and checked time beside each result. Never infer one campaign activity from another activity. Complete every required check before winner selection begins. A participant can pass one rule and fail another. Preserve accepted and rejected states separately. This lets an operator explain every inclusion and rejection later. ## Choose the Focused Picker Guide Use the [Twitter giveaway picker guide](/guides/twitter-giveaway-picker) for random winner selection, backups, exports, and public result URLs. Use the [comment and retweet picker guide](/guides/twitter-comment-retweet-picker) for reply authors, retweeters, hashtags, cursors, and stable user IDs. ## Follow Check Check one relationship at a time. Store both handles, the result, and time. A follow check proves one source-to-target relationship at one checked time. Store the source handle, target handle, stable user IDs, and response state. Repeat the same request only when the first read is uncertain. Do not treat follower counts as proof for one participant. Use separate rows when campaigns require several target accounts. This prevents one successful relationship from satisfying every follow rule. ```bash theme={null} curl "https://xquik.com/api/v1/x/followers/check?source=participant_handle&target=username" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ## Tweet-Level Checks Use tweet endpoints when one source tweet defines campaign participation. Keep each cursor with its participant page. Retrieve every available reply, retweeter, or quote page before evaluation. Save each page before requesting the next cursor. Normalize participants to stable X user IDs. Then join required activities by that stable key. Remove duplicate authors only when published uniqueness rules require it. Keep rejection reasons for missing replies, retweets, quotes, or required text. Do not replace missing proof with displayed engagement counts. `GET /api/v1/x/tweets/{id}/retweeters` `GET /api/v1/x/tweets/{id}/replies` `GET /api/v1/x/tweets/{id}/quotes` Page until `has_next_page` is `false`. Pass `next_cursor` back as `cursor`. Store the campaign ID with every page. ## Giveaway Draw Use the Draws API when campaign rules require winner selection. Define every filter before entries close. Never add hidden rules afterward. Record the number of winners and backup winners before running a giveaway. Random selection starts only after every published check completes. Do not randomly pick from an unchecked participant list. Do not pick multiple winners by repeating uncertain create requests. Save the original draw request and returned draw ID immediately. Poll that stored draw until completion or failure. Export inspected entries and selected winners as separate records. Reconcile exported row counts with the stored request before publishing results. ```bash theme={null} curl -X POST https://xquik.com/api/v1/draws \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "tweetUrl": "https://x.com/example_user/status/1893704267862470862", "winnerCount": 3, "backupCount": 2, "uniqueAuthorsOnly": true, "mustRetweet": true, "mustFollowUsername": "username", "requiredKeywords": ["entered"] }' | jq ``` Export inspected participants with `type=entries`. Export selected winners with `type=winners`. ```bash theme={null} curl "https://xquik.com/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345/export?format=csv&type=entries" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -o campaign-entries.csv ``` ## Store One Audit Row Use stable user IDs as keys. Display names and handles can change. One audit row should answer who, what, when, and how. Store the participant ID, source Tweet ID, proof endpoint, cursor, and state. Add the campaign ID and checked timestamp. Keep the published rule that required the check. Preserve rejection reasons without exposing private operator notes. ```json theme={null} { "campaign_id": "spring-launch-2026", "x_user_id": "9876543210", "username": "participant_handle", "tweet_id": "1893704267862470862", "proof_endpoint": "GET /api/v1/x/followers/check", "proof_cursor": null, "verification_state": "matched", "checked_at": "2026-05-24T19:30:00.000Z" } ``` ## Handle Costs and Retries Direct X read endpoints are metered. Budget by participants and pages. Draw execution can meter tweet, reply, retweeter, and follow checks. `402 insufficient_credits` stops the audit. Fund or narrow it first. Repeat an uncertain read with the same tweet, filter, and cursor. Search draw history before repeating an uncertain create request. Estimate costs from participant checks and expected pagination. Persist completed pages so retries never restart the whole campaign audit. Stop on `402 insufficient_credits`. Add credits or narrow the participant set. Respect `429` backoff before repeating reads. Never change campaign rules to avoid a billing or rate-limit response. ## Next Steps Run a giveaway draw with published eligibility filters. Export winners or all inspected entries. Verify one source and target relationship. Page users who retweeted one campaign tweet. # Composio Twitter MCP Alternative & Migration Guide Source: https://docs.xquik.com/guides/composio-migration Replace a Composio Twitter MCP session with Xquik for tweet search, profiles, followers, replies, monitors, webhooks, and approved account actions safely.
For the complete documentation index, see llms.txt.
Migrate a Composio Twitter MCP workflow without losing tweet IDs, followers, replies, or cursors. Preserve webhooks and write confirmations. Find Xquik REST routes through `explore`. Run approved calls through `xquik`. Composio still supports MCP sessions. Choose Xquik for follower exports and monitors. ## Evaluate Composio Alternatives for Twitter MCP Composio AI connects users to pre-built tools, handles OAuth, and exposes AI tools through MCP. The question "what is Composio AI?" needs three checks: authorization, transport, and execution. This guide compares AI workflow tools, not enterprise AI automation. Platforms for building custom AI agents still need X authorization, tweet fields, and retries. AI-powered workflow builders change only orchestration. Open-source AI agent development does too. Need a Composio open source alternative? Compare broader platforms. For Twitter, Xquik is a Twitter API alternative and X API alternative. Xquik covers tweets, followers, replies, profiles, timelines, monitors, and webhooks. Compare Composio MCP alternatives and Composio dev alternatives with these checks: * **Auth:** Record OAuth ownership. Map each application user to one X profile. * **Tools and APIs:** Record tool calls, API endpoints, and side effects. Choose unified APIs or a direct REST API. * **Agent safety:** Reapprove permissions. Test developer experience through SDK typing, logs, documentation, and rollbacks. * **Workflow safety:** Validate API calls and user profile scope in every AI assisted social media workflow. * **API docs:** Can developers find the integration platform's API documentation? * **Rollback:** Can operators run production-ready rollback steps? * **Commercial fit:** Compare pricing, features, free tier, and white-label consent. Never choose an automation tool by its free tier alone. * **Freshness:** Measure real time claims. Name every data sync: tweets, profiles, followers, or events. Compare Twitter automation tools by reads, exports, monitors, webhooks, and approved writes. ## Compare Current MCP Models
Compare current Composio and Xquik Twitter MCP workflows.
Capability Composio session Xquik
MCP endpoint Create a user session. Configure the Xquik API MCP server once.
Endpoint access Read session.mcp.url and session.mcp.headers. Complete OAuth or send a supported API key.
X account Scope the session to a user and connection. Bind each credential to one application user or tenant.
Route discovery Load Twitter toolkit schemas through the session. Use explore to find current Xquik REST routes.
Reads and exports Call the matching action. Use xquik, REST, or an extraction job.
Account actions Approve each action, then run it for the connected account. Require approval and confirm the terminal result.
Do not compare volatile tool counts. Compare the exact operations, schemas, permissions, and results your workflow uses. ## Capture the Current Composio Contract Inventory every Composio tool slug, connection, input, output, and side effect. List tweet, profile, follower, DM, list, and write calls. Keep IDs, usernames, timestamps, metrics, URLs, and cursors. Mark tweet creation, replies, likes, follows, DMs, deletes, and list changes. Map each session and connection to one application user. Save retryable statuses, limits, delays, and idempotency rules. Save sanitized tweet, profile, follower, cursor, and error examples. Exclude API keys, session headers, OAuth tokens, and raw DMs from fixtures. ## Verify the Current Composio Session Code ```bash Python theme={null} python -m pip install "composio==0.15.0" ``` ```bash TypeScript theme={null} npm install "@composio/core@0.14.1" ``` ### Python Session MCP Python `composio==0.15.0` exposes `Composio.create()`. Sessions include MCP URL and headers. ```python theme={null} import os from composio import Composio composio_client = Composio(api_key=os.environ["COMPOSIO_API_KEY"]) session = composio_client.create( user_id=os.environ["USER_ID"], toolkits=["twitter"], auth_configs={ "twitter": os.environ["COMPOSIO_TWITTER_AUTH_CONFIG_ID"], }, ) COMPOSIO_MCP_URL = session.mcp.url COMPOSIO_MCP_HEADERS = session.mcp.headers ``` Do not pass `mcp=True` to this Python version. The signature rejects it. ### TypeScript Session MCP TypeScript `@composio/core==0.14.1` uses `composio.sessions.create()` with an explicit MCP option. ```typescript theme={null} import { Composio } from "@composio/core"; const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY, }); const session = await composio.sessions.create(process.env.USER_ID!, { toolkits: ["twitter"], authConfigs: { twitter: process.env.COMPOSIO_TWITTER_AUTH_CONFIG_ID!, }, mcp: true, }); const composioMcpUrl = session.mcp.url; const composioMcpHeaders = session.mcp.headers; ``` Keep `session.mcp.headers`. The hosted endpoint may require them. ## Configure the Xquik Twitter MCP Server Add the fixed Xquik endpoint to your client. ```bash Claude Code theme={null} claude mcp add --transport http xquik https://xquik.com/mcp ``` ```bash Codex theme={null} codex mcp add xquik --url https://xquik.com/mcp codex mcp login xquik ``` ```json Cursor theme={null} { "mcpServers": { "xquik": { "url": "https://xquik.com/mcp" } } } ``` Complete Xquik login. Servers may use supported API keys. Store keys in a secret manager. Some Codex releases drop RFC 9207 `iss`. Xquik returns it. The error reads `Authorization server response missing required issuer: expected https://xquik.com`. Follow [Codex OAuth issuer validation error](/guides/troubleshooting#codex-oauth-issuer-validation-error) and track [upstream issue #31573](https://github.com/openai/codex/issues/31573). ChatGPT custom apps require OAuth. They cannot present custom Xquik API-key headers. ## Map Composio Twitter Tools to Xquik Routes Use `explore` before selecting a route. Check its path, method, parameters, response, and account permission. Use `GET /api/v1/x/tweets/search`. Preserve `q`, ordering, windows, filters, and cursors. Use `GET /api/v1/x/tweets/{id}`. Preserve the tweet ID. Use `GET /api/v1/x/users/{id}` with a username or user ID. Use `GET /api/v1/x/users/search`. Preserve the query. Use `GET /api/v1/x/users/{id}/followers`. Preserve user IDs and cursors. Use `GET /api/v1/x/users/{id}/following`. Keep following rows separate. Use `GET /api/v1/x/users/{id}/tweets`. Choose reply and parent options. Use `GET /api/v1/x/tweets/{id}/replies`. Preserve direct and nested reply IDs. Use `GET /api/v1/x/trends`. Preserve WOEID, rank, query, and description. Use catalog-listed monitor routes. Store the monitor ID before creating webhooks. Use REST or SDKs for binaries and large responses. Use extraction jobs for durable files. ## Discover Routes Before Execution The `explore` tool finds authenticated routes without running them. Ask it for route metadata before calling `xquik`. ```javascript theme={null} async () => { return spec.endpoints.filter((endpoint) => endpoint.summary.toLowerCase().includes("followers"), ); } ``` Then call the selected route through `xquik.request()`. ```javascript theme={null} async () => { return xquik.request("/api/v1/x/tweets/search", { query: { q: '"model context protocol" lang:en -filter:retweets', queryType: "Latest", limit: 100, }, }); } ``` Keep searches focused. Preserve the exact query beside every tweet page. ## Replace Provider-Specific Cursors Never pass a Composio cursor into Xquik. Start each shadow read at page one. Store each page before its `next_cursor`. Keep request inputs unchanged. Xquik MCP list and search results use `has_more` and `next_cursor`. Pass `cursor` for X resources, events, and extractions. Stop on `has_more=false`, missing cursors, or repeated cursors. Return partial counts and the stop reason. ## Normalize Results Before Cutover ### Tweet Search Rows Store `q`, window, tweet ID, text, author, timestamp, URL, and separate metrics. ### Profile and Follower Rows Store user ID, username, name, followers, following, verification, and picture. ### Reply Rows Store tweet, conversation, parent, author, timestamp, text, and URL fields. ### Trend Rows Store name, rank, query, description, count, and WOEID. ### Monitor and Webhook Rows Store monitor, endpoint, delivery, and event IDs. Verify signatures. ### Stored Event Replay Use `GET /api/v1/events` with `cursor`. Store event ID, type, monitor ID, time, `has_more`, and `next_cursor`. ## Preserve Tenant and Account Boundaries Preserve each Composio session's application user. Bind each Xquik credential to one tenant. Confirm the X account for every call. Never share OAuth tokens between tenants. Exclude tokens from prompts, traces, and clients. Inject secrets at the MCP transport boundary. Exclude agent state and chat history. ## Migrate Reads Before Writes Shadow tweet search, profiles, followers, following, replies, and trends first. Validate these invariants: * Match accounts and query windows. * Keep tweet and user IDs as strings. * Stop pagination correctly. * Preserve missing optional fields. * Deduplicate by stable IDs and keep safe error categories. Expect ranking changes. Compare IDs, coverage, fields, and timestamps within one window. ## Cut Over Writes Safely Do not run Composio and Xquik mutations in parallel. That can duplicate tweets, replies, likes, follows, DMs, or list changes. Stop old writes. Keep shadow reads. Show the action, account, content, recipients, and media. Call the documented Xquik route. Store the returned durable ID. Poll before retrying or showing success. Migrate one mutation class at a time. Never claim success from an accepted request alone. Confirm the documented terminal response. ## Preserve Media Workflows For tweets and replies, pass public HTTPS image or MP4 URLs. Store the returned ID. For DMs, upload media first. Pass `media_id` inside `media_ids`. Store the message, account, and recipient. Never reuse Composio media IDs. Follow the Xquik contract. ## Handle Xquik Errors Branch on the selected route's canonical statuses. Fix the request. Do not retry unchanged. Reauthorize Xquik or add a valid credential. Resolve the account action before retrying. Check the requested resource ID. Save returned rows. Retry only when safe. Honor retry guidance and cap backoff. Retry transient failures with an attempt cap. Store the action ID and poll its status. Log the route, method, status, and workflow ID. Never log secrets or raw DMs. ## Test, Cut Over, and Roll Back Validate normalized rows and errors. Compare providers without writes or notifications. Move one consumer to Xquik rows. Check cursors, duplicates, fields, statuses, and latency. Move consumers only after their row invariants pass. Use the approval and terminal-confirmation process above. Keep the old configuration disabled during validation. Roll back consumers, never completed writes. ## Common Composio Twitter MCP Migration Questions ### Is Xquik a Composio Alternative for Twitter MCP? Yes, for X reads, exports, monitors, webhooks, and approved actions. Compare routes. ### Do I Need an X Developer Account for Xquik? Xquik does not require X developer credentials. Authorize Xquik instead. ### Can I Reuse a Composio MCP Session URL? No. Configure `https://xquik.com/mcp` separately. Never reuse Composio session URLs. ### Can I Reuse Composio Pagination Cursors? No. Restart at page one. Store each Xquik cursor after its page. ### Can Xquik Export Twitter Followers and Following? Yes. Page through follower routes or create extraction files. ### How Do I Migrate Tweet Search Without Missing Posts? Shadow the same query and window. Compare tweet IDs and cursors. ### How Do I Avoid Duplicate Tweets During Write Cutover? Disable Composio writes. Run one approved Xquik write, then confirm it. ## Source and Contracts * [Composio Sessions Through MCP](https://docs.composio.dev/docs/sessions-via-mcp) * [Composio Twitter Toolkit](https://composio.dev/toolkits/twitter) * [Xquik MCP Overview](/mcp/overview) * [Xquik MCP Tools](/mcp/tools) * [Xquik Tweet Search API](/api-reference/x/search-tweets) * [Xquik Followers API](/api-reference/x/followers) * [Xquik Error Handling](/guides/error-handling) # CrewAI Twitter MCP Multi-Agent Guide for Python Source: https://docs.xquik.com/guides/crewai Build a CrewAI Twitter MCP crew for tweet search, profiles, followers, monitors, exports, and reviewed X actions with typed Python agent handoffs safely.
For the complete documentation index, see llms.txt.
Build a CrewAI MCP integration through Xquik's remote Twitter MCP server. Give CrewAI agents controlled tweet searches, profiles, follower exports, monitors, and reviewed X actions. Preserve every tweet ID, profile ID, cursor, and job ID. ## Why Use CrewAI With MCP for a Twitter API? CrewAI offers an agent framework for complex tasks. Give each agent one role. Xquik supplies Twitter API operations through `explore` and `xquik`. | Boundary | CrewAI control | Benefit | | ---------- | -------------------------- | -------------------------------------------------- | | Remote MCP | `MCPServerHTTP` | Reach tweet, profile, follower, and monitor routes | | Handoff | Pydantic `output_pydantic` | Reject malformed tweets and cursors | | Sequence | `Process.sequential` | Pass exact tweets between specialists | | Discovery | Static tool filter | Expose schemas without execution | | Review | Tool-free task | Review X actions before writes | | Failures | `has_tool_failures` | Stop incomplete research | This CrewAI multi agent pattern fits research, verification, and reporting. Use direct REST for deterministic jobs without model decisions. ## CrewAI Twitter API Prerequisites * Python 3.10 through 3.13 * An [Xquik API key](/x-api-quickstart) beginning with `xq_` * An LLM provider key supported by CrewAI * A connected X account for private reads or X write actions Public X reads need no X Developer credentials. Authenticate with Xquik. Connect an X account only for routes that require one. ## Install CrewAI MCP Support CrewAI core includes a native MCP client. This CrewAI Python setup needs no custom adapter. ```bash theme={null} python -m pip install "crewai>=1.15,<1.16" ``` CrewAI 1.15 requires MCP 1.28. Avoid MCP 2.x with this release. Store secrets outside source control. ```bash .env theme={null} XQUIK_API_KEY=xq_YOUR_KEY_HERE OPENAI_API_KEY=YOUR_OPENAI_KEY ``` ```text .gitignore theme={null} .env xquik-*-handoff.json ``` ## Build a Typed CrewAI Tweet Search Agent Start with the expected output, then build the task. CrewAI validates the final handoff against its Pydantic model. ```python theme={null} import os from pathlib import Path from typing import Literal from crewai import Agent, Crew, Process, Task from crewai.mcp import MCPServerHTTP from pydantic import BaseModel class TweetRow(BaseModel): tweet_id: str text: str author_username: str | None created: int | None url: str | None class TweetSearchHandoff(BaseModel): query: str route_used: Literal["GET /api/v1/x/tweets/search"] tweets: list[TweetRow] has_more: bool next_cursor: str | None pages_fetched: int stop_reason: Literal[ "complete", "requested_limit", "cursor_stalled", ] xquik_mcp = MCPServerHTTP( url="https://xquik.com/mcp", headers={"x-api-key": os.environ["XQUIK_API_KEY"]}, streamable=True, cache_tools_list=True, ) researcher = Agent( role="Twitter API Researcher", goal="Return exact tweet records and resumable pagination state", backstory=( "You inspect Twitter conversations through documented Xquik routes. " "You preserve source IDs and never invent missing fields." ), llm="openai/gpt-5", mcps=[xquik_mcp], allow_delegation=False, verbose=False, ) search_task = Task( description=( "Use GET /api/v1/x/tweets/search. " "Search 50 latest tweets about CrewAI Twitter MCP. " "Preserve exact tweet IDs, created timestamps, and cursors. " "Stop at the requested limit. Stop if a cursor repeats." ), expected_output="A validated tweet search handoff with pagination state.", agent=researcher, output_pydantic=TweetSearchHandoff, ) crew = Crew( agents=[researcher], tasks=[search_task], process=Process.sequential, verbose=False, ) result = crew.kickoff() if result.has_tool_failures: raise RuntimeError("Twitter MCP tool failed. Inspect result.tool_failures.") handoff = TweetSearchHandoff.model_validate(result.to_dict()) Path("xquik-crewai-handoff.json").write_text( handoff.model_dump_json(indent=2), encoding="utf-8", ) ``` `Agent.mcps` discovers CrewAI MCP tools before execution. `MCPServerHTTP` uses Streamable HTTP by default. The MCP runtime returns normalized snake\_case fields through `xquik.request()`. It maps `createdAt` to the Unix-second field `created`. Always inspect `has_tool_failures`. Never pass incomplete results into follower exports or X actions. ## Search Tweets With Focused Queries Send a precise `q`. Keep the exact query in the handoff. | Intent | Example `q` | | ------------------------ | -------------------------------------------------- | | Framework posts | `"CrewAI" MCP` | | Account timeline | `from:crewAIInc since:2026-07-01 until:2026-08-01` | | Twitter API Python posts | `"Twitter API" Python lang:en min_faves:25` | | Questions | `"multi-agent workflow" ? -filter:retweets` | Use `queryType=Latest` for real time monitoring. Use `Top` for engagement-ranked research. See the [tweet search API contract](/api-reference/x/search-tweets). Pass `next_cursor` unchanged. Stop when `has_more` is false or cursors repeat. Continue empty pages with true `has_more`. Deduplicate by `tweet_id`. ## Build a Role-Based Tweet Research Crew Give only the researcher access to Twitter MCP search tools. Feed its validated task into a tool-free analyst. ```python theme={null} class TweetAnalysis(BaseModel): query: str analyzed_tweet_ids: list[str] recurring_topics: list[str] top_author_usernames: list[str] next_cursor: str | None analyst = Agent( role="Tweet Conversation Analyst", goal="Analyze only the supplied tweet rows", backstory="You compare exact tweets without fetching extra records.", llm="openai/gpt-5", allow_delegation=False, verbose=False, ) analysis_task = Task( description=( "Analyze the supplied tweet rows. " "Keep every analyzed tweet_id. Preserve the next_cursor." ), expected_output="A typed topic analysis tied to source tweet IDs.", agent=analyst, context=[search_task], output_pydantic=TweetAnalysis, ) research_crew = Crew( agents=[researcher, analyst], tasks=[search_task, analysis_task], process=Process.sequential, verbose=False, ) ``` The `context` list passes the first result into the second. The analyst cannot fetch unrelated tweets or profiles. Choose hierarchical delegation when specialists work independently. Use sequential tasks for cursor-dependent agent collaboration. ## Apply CrewAI MCP Integration Patterns Each CrewAI agent with MCP server access gets one permission boundary. Teams of AI agents must not share write-capable keys. This CrewAI MCP integration connects external APIs through synchronized schemas. Prefer CrewAI tools before building a CrewAI custom tool. The `tools` tool list shows permitted agent actions. Review agent tools before each tool integration. `import tool` shortcuts and `def run` wrappers duplicate native MCP behavior. Avoid broad web searches when tweet IDs matter. Set `verbose=True` only while debugging complex tasks. Real world AI applications need safe multi agent systems. ## Keep Twitter Actions Outside the Research Crew The `xquik` tool runs every route allowed by its key. Never give write permissions to autonomous research crews. Use guest `paid_reads` for eligible GET routes. The [guest wallets guide](/guides/guest-wallets) documents this scope. ```python theme={null} class TweetWritePlan(BaseModel): account_id: str text: str reply_to_tweet_id: str | None media_urls: list[str] idempotency_key: str planner = Agent( role="Twitter Action Planner", goal="Prepare one reviewable X action without executing it", backstory="You preserve approved text, target IDs, and account IDs.", llm="openai/gpt-5", tools=[], allow_delegation=False, ) plan_task = Task( description="Prepare a tweet or reply plan from reviewed source tweets.", expected_output="One typed action plan. Do not execute any X request.", agent=planner, output_pydantic=TweetWritePlan, human_input=True, ) ``` `human_input=True` reviews the result, while `tools=[]` blocks execution. After approval, send one REST request. Never retry pending writes automatically. ## Expose Endpoint Discovery Only Expose only `explore` for endpoint discovery. ```python theme={null} from crewai.mcp import MCPServerHTTP from crewai.mcp.filters import create_static_tool_filter discovery_mcp = MCPServerHTTP( url="https://xquik.com/mcp", headers={"x-api-key": os.environ["XQUIK_API_KEY"]}, tool_filter=create_static_tool_filter( allowed_tool_names=["explore"], ), cache_tools_list=True, ) ``` This filter cannot execute Twitter API calls. `explore` returns methods, parameters, and response fields. Adding `xquik` enables authorized execution. ## Use One MCP Configuration Per Tenant Resolve the tenant first. Then create its `MCPServerHTTP` configuration. ```python theme={null} def build_tenant_mcp(user_api_key: str) -> MCPServerHTTP: return MCPServerHTTP( url="https://xquik.com/mcp", headers={"x-api-key": user_api_key}, streamable=True, cache_tools_list=True, ) ``` Never share agents across tenant keys. Keep keys outside prompts, output, memory, traces, and handoffs. Reuse one configuration per crew run. ## Store a Resumable CrewAI Handoff Store identifiers and checkpoints outside task prose. Store `q`, `tweet_id`, `created`, `has_more`, and `next_cursor`. Store `id` as `user_id`. Keep `username`, `followers`, `has_more`, and `next_cursor`. Store `monitor_id`, `event_id`, `occurred_at`, `next_cursor`, and `cursor`. Store `webhook_id`, `delivery_id`, and `stream_event_id`. Protect `secret` separately. Store `extraction_id`, `status`, `poll`, and `export_after_complete`. Store `tweet_id`, `write_action_id`, `status`, `charged_credits`, and `poll`. Never resend pending writes. Persist `result.pydantic` or `result.to_dict()`. Avoid free-form `result.raw`. ## Handle Twitter API Errors and Tool Failures The tweet search contract documents these responses. Keep their meanings separate. | Status | Meaning | Crew action | | ------ | --------------------------- | --------------------------------- | | `400` | Missing or invalid query | Fix `q`; never retry unchanged | | `401` | Guest authentication failed | Provide an Xquik key or connect X | | `402` | Credits are unavailable | Stop and request account action | | `424` | X dependency failed | Retry with bounded backoff | | `429` | Twitter rate limit applies | Wait, then resume the cursor | | `502` | Invalid X response | Retry later without changing IDs | Inspect `result.tool_failures` after failures. Log the route, safe message, and task index. After `429`, preserve `next_cursor` and completed tweet IDs. Deduplicate by `tweet_id` after recovery. POST and DELETE routes use different statuses. Read each route before retrying. See [error handling](/guides/error-handling). ## Verified CrewAI Package Versions These versions were checked on August 2, 2026. | Package | Checked compatible version | Compatible range used here | | ---------- | -------------------------- | ------------------------------- | | `crewai` | 1.15.10 | `>=1.15,<1.16` | | `mcp` | 1.28.1 | `>=1.28.1,<1.29` through CrewAI | | `pydantic` | 2.12.5 | `>=2.11.9,<2.13` through CrewAI | CrewAI 1.15 supports Python 3.10 through 3.13. Review release notes before widening these ranges. ## CrewAI Twitter MCP Questions ### What Does the CrewAI MCP Server Do? It exposes Xquik route discovery and execution. Agents search tweets, export followers, replay monitors, and plan reviewed writes. ### How Do I Search Tweets With Python and CrewAI? Call `GET /api/v1/x/tweets/search`. Preserve `q`, `tweet_id`, `created`, and `next_cursor`. ### Can CrewAI Export Twitter Followers? Use the [followers API](/api-reference/x/followers). Choose extraction jobs for CSV, JSON, or XLSX exports. ### How Should CrewAI Handle Agent Roles and Permissions? Give researchers MCP access. Give planners no tools. Review writes through the [create tweet contract](/api-reference/x-write/create-tweet). ### How Should CrewAI Handle Twitter API Rate Limits? Save the cursor and completed tweet IDs. Resume after reset guidance. Never restart pagination. ### Why Are CrewAI MCP Tools Missing? Check the URL, key, versions, and transport. Review [CrewAI GitHub issues](https://github.com/crewAIInc/crewAI/issues) for current defects. ### CrewAI MCP or Direct REST? Choose CrewAI MCP for role-based decisions and typed handoffs. Choose REST for fixed routes, scheduled exports, and predictable latency. # X Direct Message API: Send DMs, Read History & Media Source: https://docs.xquik.com/guides/direct-message-workflow Look up a user ID, read DM history, send an X direct message, store the returned message ID, attach one uploaded media item, and handle DM errors. See examples.
For the complete documentation index, see llms.txt.
Use this workflow for support, sales, community, or agent systems. Read DM history and send direct messages from a connected X account. Xquik reads participant-scoped conversations through `GET /x/dm/{userId}/history`. It sends text DMs through `POST /x/dm/{userId}`. It can attach one uploaded media item through `media_ids`.
* 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)
## Choose the DM path Call `POST /x/dm/{userId}` with `account` and non-empty `text`. Store `messageId`, `success`, sender account, and recipient ID. Call `POST /x/media` first, then send `media_ids: [""]` on `POST /x/dm/{userId}`. Use exactly 1 item and store `media_id` beside `messageId`. Call `GET /x/dm/{userId}/history` with `account` before replying when the workflow needs private conversation context. Store `messages`, `has_next_page`, and `next_cursor`. Call `GET /x/users/{id}` first when the app only has a username. DM sends require the numeric recipient ID in the path. ## When to use this workflow Use `GET /x/users/{id}` to convert a username to the numeric recipient ID required by DM writes. Use `GET /x/dm/{userId}/history` with `account` to sync participant-scoped messages. Use `POST /x/dm/{userId}` with `account` and `text`, then store the returned `messageId`. Upload media first, then pass one `mediaId` in `media_ids`. Retry only `429` and `503`; fix `400`, `402`, `403`, and `422` first. ## Data you get `GET /x/users/{id}` returns recipient `id`, `username`, `name`, and profile fields. `GET /x/dm/{userId}/history` returns `messages`, `has_next_page`, and `next_cursor`. `POST /x/dm/{userId}` returns `messageId` and `success` for outbound handoff storage. `POST /x/media` returns `mediaId`, `mediaUrl`, and `success` before the one-item DM attachment send. ## End-to-end direct message handoff Use one checkpoint object after recipient lookup, history sync, a text send, and an optional media send. Keep full DM bodies in restricted support, CRM, warehouse, or agent memory systems; shared run logs should carry IDs, status, cursors, and media references. ```json theme={null} { "workflow": "direct_message_handoff", "recipient_lookup": { "id": "987654321", "username": "username" }, "history_page": { "account": "myxhandle", "conversation_user_id": "987654321", "messages_count": 25, "page_cursor": null, "next_cursor": "1893726451029384190", "has_next_page": true }, "text_send": { "endpoint": "/api/v1/x/dm/987654321", "account": "myxhandle", "recipient_user_id": "987654321", "message_id": "1893726451029384192", "success": true, "send_status": "sent" }, "media_send": { "upload_media_id": "1893726451023847424", "media_ids": ["1893726451023847424"], "media_url": "https://media.example.com/support-image.png", "message_id": "1893726451029384193", "success": true, "send_status": "sent" }, "audit_row": { "record_type": "dm_handoff_checkpoint", "sender_account": "myxhandle", "recipient_user_id": "987654321", "message_id": "1893726451029384193", "media_id": "1893726451023847424", "source_endpoint": "/api/v1/x/dm/987654321", "handoff_format": "jsonl", "message_text_storage": "restricted_system_only" }, "handoff_state": "store_message_ids_and_private_rows" } ``` Store the numeric `id` and `username` from user lookup. Store `messages_count`, `next_cursor`, and `has_next_page` for each synced history page. Store the `message_id`, `success`, `send_status`, sender account, and recipient ID after `POST /x/dm/{userId}`. Store exactly one uploaded `media_id` beside the returned DM `message_id`. Keep shared audit rows limited to IDs, cursors, status, endpoints, and media references. Do not pass `reply_to_message_id`, empty `media_ids`, or more than one media ID. ## Step 1: Look up the recipient user ID `POST /x/dm/{userId}` requires the numeric X user ID in the path. If you only have a username, call `GET /x/users/{id}` first. ```bash theme={null} curl https://xquik.com/api/v1/x/users/username \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```json theme={null} { "id": "987654321", "username": "username", "name": "Xquik" } ``` Trust the DM write response when recording delivery. ## Step 2: Read direct message history Use the sender account as the `account` query parameter. The connected account must belong to the conversation. Direct messages remain private and user-scoped. ```bash theme={null} curl -G https://xquik.com/api/v1/x/dm/987654321/history \ --data-urlencode "account=myxhandle" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```json theme={null} { "messages": [ { "id": "1893726451029384191", "text": "Can you send the setup link?", "senderId": "987654321", "receiverId": "123456789", "createdAt": "2026-02-24T10:00:00.000Z" } ], "has_next_page": true, "next_cursor": "1893726451029384190" } ``` For older messages, pass the previous `next_cursor` as `cursor`. Store each message `id`, `text`, `senderId`, `receiverId`, `createdAt`, and optional `mediaUrl` in your support ticket, CRM note, or JSON export. DM history and outbound `message_text` values can contain private customer or community conversations. Store them only in private support, CRM, warehouse, or agent memory systems. Shared logs, public artifacts, and status dashboards should keep `message_id`, `sender_id`, `receiver_id`, `created_at`, `media_url`, and job status instead of full DM bodies. ### Sync and retry rules Treat `next_cursor` as opaque. Pass it back as `cursor` for the next page. Do not decode it or build your own cursor. Store every message `id`. Use the ID to dedupe support tickets, CRM notes, warehouse rows, or JSON exports. Use a participant account. `GET /x/dm/{userId}/history` returns `400 account_required` without `account` and `403 dm_not_permitted` when the connected account is not in the conversation. Use `cursor`; keep `maxId` only for older integrations that already depend on it. Retry `429` and `503`; do not retry `403 dm_not_permitted` with the same non-participant account. ## Step 3: Send a text direct message Use a connected account as the sender. The `account` value can be the connected account username or account ID. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/dm/987654321 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "text": "Thanks for reaching out. Here is the next step." }' | jq ``` ```json theme={null} { "messageId": "1893726451029384192", "success": true } ``` ### Store the outbound handoff After a `200` response, persist one outbound record before handing control back to a CRM, ticket, queue, or agent. `POST /x/dm/{userId}` returns `messageId` and `success`; use your own job timestamp if the downstream system needs `sent_at`. ```json theme={null} { "status": "sent", "recipient_user_id": "987654321", "sender_account": "myxhandle", "message_id": "1893726451029384192", "message_text": "Thanks for reaching out. Here is the next step." } ``` Text-only DM sends omit media fields. Add `media_ids` in the request and `media_id` in the handoff only for the media send in Step 4. When you also sync history, normalize each `messages[]` item separately with `message_id`, `sender_id`, `receiver_id`, `created_at`, optional `media_url`, and `conversation_user_id`. ### JSON Lines handoff For queue, warehouse, CRM, or agent memory, write one record per history message or outbound send to `xquik-dm-handoff.jsonl`. ```json theme={null} { "record_type": "dm_history", "conversation_user_id": "987654321", "sender_account": "myxhandle", "message_id": "1893726451029384191", "sender_id": "987654321", "receiver_id": "123456789", "created_at": "2026-02-24T10:00:00.000Z", "message_text": "Can you send the setup link?", "page_next_cursor": "1893726451029384190" } ``` ```json theme={null} { "record_type": "dm_send", "status": "sent", "conversation_user_id": "987654321", "sender_account": "myxhandle", "recipient_user_id": "987654321", "message_id": "1893726451029384192", "message_text": "Thanks for reaching out. Here is the next step.", "handoff_format": "jsonl" } ``` History rows include `media_url` only when the message has media. Text-only send rows omit `media_id` and `media_ids`; media send rows use the Step 4 shape. ## Step 4: Send a direct message with media Upload media first, then send exactly one uploaded media ID in `media_ids`. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/media \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "url": "https://example.com/support-image.png" }' | jq ``` ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/dm/987654321 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "text": "Here is the requested image.", "media_ids": ["1893726451023847424"] }' | jq ``` DMs accept one uploaded media ID. Do not pass multiple IDs, an empty array, or `reply_to_message_id`. The media DM send returns the same `messageId` and `success` fields as a text DM. Store the uploaded `media_id` beside that returned message ID so attachment audits can join the upload, recipient, sender account, and outbound message. ```json theme={null} { "messageId": "1893726451029384193", "success": true } ``` ```json theme={null} { "record_type": "dm_media_send", "conversation_user_id": "987654321", "sender_account": "myxhandle", "recipient_user_id": "987654321", "message_id": "1893726451029384193", "message_text": "Here is the requested image.", "media_ids": ["1893726451023847424"], "media_id": "1893726451023847424", "handoff_format": "jsonl" } ``` ## Twitter DM API Questions ### How Does the X Direct Message API Work? The X direct message API sends one-to-one messages from a connected X account. Use a numeric user ID for each recipient. Send text through `POST /x/dm/{userId}`. This Twitter DM API workflow also reads participant-scoped message history. Store each returned `messageId`. Keep that ID for matching records and audit logs. Use a connected Twitter account for each private message. Reuse the idempotency key only for the identical request. Poll non-terminal write actions through their status URL. Use a new key only when `safeToRetry` is `true`. ### How Do I Send a Twitter DM Through an API? A Twitter API DM send starts with recipient lookup. Call `GET /x/users/{id}` when you only know the username. Then call `POST /x/dm/{userId}` with `account` and `text`. The Twitter direct message API returns `messageId` and `success`. Store both before any CRM, queue, or agent handoff. ### Can the API Receive Twitter DMs in Real Time? Xquik reads saved history on demand. This route does not register real-time DM events. Poll `GET /x/dm/{userId}/history` when the workflow needs new messages. Omit `cursor` from the first history request. Then pass each response's `next_cursor` value as the next request's `cursor`. A Twitter DM history API client should dedupe each message `id`. ### Why Does Sending a DM Return 403 or 422? A `403` can indicate a disconnected or non-participant account. Reconnect accounts that need reauthorization. Read private conversations through an account that belongs to them. A `422` means X rejected the request. Check recipient permissions, message text, and sender account state. Do not retry an unchanged rejected request. ### Can This API Create Group DM Conversations? This route accepts one recipient `userId`. The current Xquik contract does not create group conversations. Do not pass participant arrays to the one-to-one send route. Check the OpenAPI contract before sending message conversations. ### How Do I Send a DM With Media? Upload one image, GIF, or video through `POST /x/media`. Use the returned `mediaId` in a one-item `media_ids` array. To send DM with media, include non-empty text too. Send one media ID. Empty or multiple media IDs fail. Keep upload and message IDs together for private attachment audits. ### Is Twitter DM Automation Suitable for Welcome Messages? Use Twitter DM automation only for expected, approved customer conversations. Never send unsolicited welcome messages to new followers. Review [X developer guidance](https://docs.x.com/developer-guidelines) before sending DMs. Keep message bodies inside restricted support or CRM systems. Store IDs and delivery status in shared operational logs. Provide an easy opt-out for automated message workflows. ## Costs `GET /x/users/{id}` costs 1 credit per call. `GET /x/dm/{userId}/history` costs 1 credit per message returned. `POST /x/media` costs 10 credits per upload call before a media DM send. `POST /x/dm/{userId}` costs 10 credits per call and returns `messageId`. ## Error handling Check `account`, `text`, `userId`, and one-item `media_ids`. Pass the connected sender handle as `account` when reading DM history. Subscribe or top up credits before retrying. Reconnect the sender account from the dashboard. Use a connected account that participates in the conversation, or reconnect the account. `422 x_dm_not_allowed`. The recipient may not accept DMs from this connected account. Do not retry unchanged; use another permitted account or ask the recipient to allow messages. Check the recipient, account state, and message content before retrying. Retry with exponential backoff and respect `Retry-After` when present. ## Handoff checklist Store the connected X account username or ID sent in `account`. Store the numeric recipient ID used in the `POST /x/dm/{userId}` path. For DM history exports, store `messages`, `has_next_page`, and `next_cursor`; pass `cursor` to fetch older messages. Store the required non-empty `text` value. Store the optional one-item `media_ids` array containing a `mediaId` from `POST /x/media`. Store `messageId`, `recipient_user_id`, `sender_account`, `message_text`, and optional `media_id` in private audit records or support systems. Store history and send records in `xquik-dm-handoff.jsonl` for queues, warehouse loads, CRM syncs, or agent memory. **Related:** [Send Direct Message](/api-reference/x-write/send-dm) · [Get DM History](/api-reference/x/dm-history) · [Get User](/api-reference/x/twitter-profile-lookup) · [Media Upload Workflow](/guides/media-upload-workflow) # X API Error Handling for Tweet & Follower Workflows Source: https://docs.xquik.com/guides/error-handling Handle Xquik API errors with exact status guidance and safe retries. Recover cursors, confirm writes, repair monitors, restore webhooks, and check dependencies.
For the complete documentation index, see llms.txt.
Use each `error` code to choose recovery. Fix your request body. See [validation errors](#common-error-codes). Read the challenge and payment options. Never start checkout automatically. See [common errors](#common-error-codes). Implement backoff. See [retry strategy](#retry-with-exponential-backoff) or [rate limits](/guides/rate-limits). ## Quick reference Start with HTTP status. Retry only when stated. Retry: no. Fix the body, query, or path. Covers `invalid_input`, `invalid_json`, `invalid_id`, `invalid_tweet_url`, `invalid_tweet_id`, `invalid_username`, `invalid_user_id`, `invalid_tool_type`, `invalid_format`, `invalid_params`, `missing_query`, `missing_ids`, `missing_params`, `too_many_ids`, `unsupported_field`, and `invalid_coverage_cursor`. Retry: no. Check `x-api-key`, regenerate revoked keys, or re-authenticate the connected X account. Covers `unauthenticated` and `x_auth_failure`. Retry: no. Read `payment_options`, then get explicit user confirmation. Covers `no_subscription`, `subscription_inactive`, `payment_failed`, `no_credits`, and `insufficient_credits`. Retry: no. Delete an extra key, check billing status, use a participating DM account, re-authenticate the X account, or resolve account health on x.com. Covers `api_key_limit_reached`, `dm_not_permitted`, `account_needs_reauth`, and `account_restricted`. Retry: no. Verify the resource ID, connected account, username, tweet ID, media, article, draft, or cached style. Covers `not_found`, `account_not_found`, `user_not_found`, `tweet_not_found`, `no_media`, `article_not_found`, `draft_not_found`, `style_not_found`, and `no_cached_style`. Busy cursor: follow `Retry-After` and retry once. Gone cursor: restart cursorless and deduplicate IDs. Retry: no. Fix the account capability, target, content, DM permissions, or media URL before sending again. Covers `x_account_feature_required`, `x_account_suspended`, `x_account_protected`, `x_duplicate_action`, `x_dm_not_allowed`, `x_target_not_found`, `x_content_too_long`, `x_rejected`, and `media_download_failed`. Retry: no. Store the action and poll `statusUrl` while `terminal` is `false`. Follow `Retry-After`, `pollAfterMs`, and `nextAction`. Retry: mixed. Retry `rate_limit_exceeded` and `x_rate_limited` after `Retry-After` or exponential backoff. Wait out `login_cooldown` via `retryAfterMs`. Do not retry `x_daily_limit` on the same X account for 24 hours. Retry: yes for `internal_error`, `x_api_rate_limited`, `x_api_unavailable` and `x_api_unauthorized`. For writes, retry only when `safeToRetry` is `true`, using a new `Idempotency-Key`. ## Common error codes This section covers common recovery paths. The OpenAPI [`Error`](https://docs.xquik.com/openapi.yaml) schema lists every public code. **Default response:** ```json theme={null} { "error": "error_code", "message": "Human-readable description" } ``` Send `xquik-api-contract: 2026-04-29` for a structured `error` object. Some responses also include `message`, `reason`, `retryAfter`, or `retryAfterMs`. Request body, query, or path validation failed. Fix the request shape before retrying. Request body failed validation. Check required fields, types, and enum values against the endpoint docs. Request body is not valid JSON. Rebuild the body and send a parseable JSON object. Path ID is not valid. Use the numeric string returned by the create or list endpoint. Tweet URL is malformed. Use the full format `https://x.com/user/status/ID`. Tweet ID is empty or malformed. Extract the final numeric status ID before calling tweet, media, or article endpoints. Username or user path value is empty or invalid. Send a username without the `@` prefix, or send a numeric user ID where supported. User lookup input is invalid or does not resolve. Check the username or numeric user ID before retrying. Extraction tool type is not recognized. Use one of the 23 valid tool types from [Create Extraction](/api-reference/extractions/create). Export format is unsupported. Use `csv`, `json`, `md`, `md-document`, `pdf`, `txt`, or `xlsx`. Export query parameters are invalid. Check the `format` and `type` values for that export endpoint. Required search query is missing. Add the `q` parameter before calling search or community endpoints. Required multi-ID query is missing. Provide comma-separated numeric IDs in the `ids` parameter. Required query parameters are missing. Check the endpoint docs; follower checks require both source and target. Too many IDs were requested at once. Split requests into groups of 100 IDs or fewer. Request body contains a field this endpoint does not accept. For tweet posts, send public media URLs in `media`, not uploaded media IDs. Cursor is malformed. Restart without it and deduplicate stored IDs. Missing or invalid credentials. Check your API key or session. API key or bearer token is missing or invalid. Send `x-api-key` or regenerate a revoked key. Connected X account session expired or was invalidated. Re-authenticate the account from the [dashboard](https://xquik.com/dashboard). Non-MPP paid reads return `401` with `WWW-Authenticate: Bearer` and a guest wallet action. This is not a Payment challenge. Authenticate or get confirmation before calling the action. A `402` creates no checkout. Account and OAuth responses advertise account billing actions. Guest responses advertise only `POST /api/v1/guest-wallets/topups`. Direct MPP responses include a Payment challenge and guest option. Get confirmation before calling any action. No plan. Check credits, then [top up](/api-reference/credits/topup) or [subscribe](/api-reference/account/subscription-checkout). Plan inactive. Remaining credits work. Top up or reactivate on the [billing page](https://dashboard.xquik.com/en/account?tab=subscription). Payment processing failed. Update the payment method from the [dashboard](https://xquik.com/subscription). No credit balance is available. Check balance with [Get Account](/api-reference/account/get), then use [Top Up Credits](/api-reference/credits/topup) after confirmation. Balance is below the required operation cost. Account callers can check [Get Account](/api-reference/account/get). Guest callers can check [Guest Wallet Status](/api-reference/guest-wallets/status). Use only the payment action advertised for that credential. Action not allowed under current plan or limits. The account already has 100 active API keys. Revoke an active key before creating another. DM history requires a connected account that participates in the conversation. Use a participating account or reconnect it from the [dashboard](https://xquik.com/dashboard). Connected X account session needs re-authentication. Reconnect the account from the [dashboard](https://xquik.com/dashboard), then retry. Connected X account is locked, suspended, recovering, or temporarily blocked. Resolve account health on x.com or wait before retrying. The requested resource does not exist, is unavailable to this API key, or is not the expected X object type. Generic resource lookup failed. Verify the ID belongs to your account and has not been deleted. Connected X account was not found for this user. Call [List X Accounts](/api-reference/x-accounts/list) and use a valid connected account. X username or numeric user ID does not resolve. Confirm the username with the user or try a different handle. Tweet ID does not resolve. Check the numeric tweet ID; the tweet may have been deleted. Tweet exists but has no downloadable media attachments. Use a tweet that contains media. Tweet ID is valid but is not an X Article. Ask for an X Article URL or use a normal tweet or thread endpoint. Draft ID does not exist. Verify the draft ID or create a new draft. Writing style ID was not found. Analyze tweets first with [Analyze Style](/api-reference/styles/analyze). No cached writing style exists for username lookup. Analyze tweets first with [Analyze Style](/api-reference/styles/analyze). Reuse duplicate monitors. Recover cursors by status and error code. Duplicate account or keyword monitor. List existing monitors, reuse the monitor ID, or update event types with [Update Monitor](/api-reference/monitors/update) or [Update Keyword Monitor](/api-reference/monitors/update-keyword). Follow the exact `Retry-After` seconds. Retry the same cursor once. No `Retry-After`. Restart cursorless and deduplicate IDs. Write validation failed. Change the account, target, content, DM permission, or media input before retrying. Account capability is missing. Use an account with the required capability or adjust the request. Connected X account is suspended or restricted. Resolve account status on x.com before sending more writes from it. Target account is protected. Request access first or choose a target account that the connected account can interact with. Operation is already complete. Do not retry unchanged; check the target state before sending anything again. Recipient does not accept DMs from this account. Use a permitted connected account or ask the recipient to allow messages. Tweet or user target does not exist. Verify the ID or username before sending another request. Content exceeds the character limit. Shorten the text or use an account that supports the requested content length. X rejected the write without a specific reason. Change the request. Retry only when the durable action marks it safe. Public media URL could not be downloaded. Fix the HTTPS URL or pass the file via multipart/form-data. Do not retry the same URL. The durable write remains active. Its status is `accepted`, `dispatching`, or `pending_confirmation`. Store `id`, `request.hash`, `account`, `target`, and `billing`. Poll `statusUrl` after `Retry-After` or `pollAfterMs`. Stop when `terminal` is `true`. Retry only when `safeToRetry` is `true`, using a new key. Verify the result when `nextAction.type` is `verify_result`. Request rate exceeded. See [Rate Limits](/guides/rate-limits) for tier details. Xquik tier or action limit was reached. Wait the `Retry-After` seconds from the response; JSON also includes `retryAfter` when available. A recent login attempt triggered cooldown. Wait `retryAfterMs` or the `Retry-After` header before reconnecting or reauthenticating. X throttled the write. Follow `Retry-After`, then retry with backoff. Connected X account reached its daily posting limit. Wait 24 hours before retrying that account, or use another connected account. Transient failures. Retry with exponential backoff (max 3 attempts). Server error. Retry with backoff and [contact support](mailto:support@xquik.com) if it persists. Read service rate limited. Retry in a few minutes. Read service temporarily unavailable or busy. Respect `Retry-After` when present, otherwise retry with backoff. Read service authentication failed. Retry later and [contact support](mailto:support@xquik.com) if it persists. Write action failed unexpectedly. Follow `safeToRetry` and `nextAction`. Contact support if the action remains unsafe to retry. Completion could not be confirmed. Poll the durable action, then verify the result before sending anything again. Temporary write failure. Retry only when `safeToRetry` is `true`. ## Write lifecycle recovery Every write can return a durable action. Inspect the JSON lifecycle fields before generic success or error handling. Store `id`, `status`, `request.hash`, `account`, `target`, `billing`, and `statusUrl`. Poll `statusUrl` while `terminal` is `false`. Respect `Retry-After` and `pollAfterMs`. Store `result` and settled `billing` after `terminal` becomes `true`. Retry only when `safeToRetry` is `true`, using a new `Idempotency-Key`. Verify the result when `nextAction.type` is `verify_result`. ## Retry with exponential backoff For reads, retry only on `429` and `5xx` responses. Use `Retry-After`, then exponential backoff with jitter. For writes, follow the durable action's `safeToRetry` and `nextAction` fields instead. **Formula:** `delay = baseDelay * 2^attempt + random(0, jitter)` ```ts theme={null} function retryDelayMs(response: Response, attempt: number): number { const retryAfter = response.headers.get("Retry-After"); if (retryAfter) { return Number.parseInt(retryAfter, 10) * 1000; } return 1000 * 2 ** attempt + Math.floor(Math.random() * 1000); } function shouldRetry(response: Response): boolean { return response.status === 429 || response.status >= 500; } ``` ## Rate limit handling When a request is rate limited, Xquik returns `429 Too Many Requests` with a `Retry-After` header. Some JSON bodies also include `retryAfter` in seconds or `retryAfterMs` in milliseconds for login cooldowns. `429 Too Many Requests` means the request is rate limited or waiting on an account cooldown. The `Retry-After` header gives seconds to wait before sending the same request again. Branch on `response.status === 429` before normal success handling. Parse `Retry-After` as seconds. If it is missing, read `retryAfter` or `retryAfterMs` from the JSON body. Do not send another request for the same operation until the wait expires. If the same request returns another 429, apply exponential backoff and stop after 3 attempts. ## Best practices Do not retry a `4xx` response unchanged. Fix authentication, billing, permissions, or input first. Retry `429` and `5xx` only as described above. Log the status and error code. Never log credentials or private payloads. Reuse `monitor_already_exists`. For a busy cursor, follow `Retry-After` and retry once. Set a bounded client timeout. Use status endpoints for long-running jobs. Creating a monitor for the same username returns `409`. Deleting a non-existent resource returns `404`. Both are safe to retry. Detailed rate limit tiers and client-side rate limiting. Base URL, authentication, and API conventions. # Twitter Scraper API for Tweets, Replies & Followers Source: https://docs.xquik.com/guides/extraction-workflow Use the Twitter scraper API to scrape tweets, export follower and following profiles, collect reply authors, paginate JSON, and download CSV, JSON, or XLSX.
For the complete documentation index, see llms.txt.
Run bulk data extractions from X in 5 stages: check credits, estimate costs, run the job, retrieve JSON pages, and export files. Use this workflow to scrape tweets, export followers, pull tweet replies, save CSV/JSON/XLSX files, or hand paginated JSON to a CRM, warehouse, queue, or AI agent. This tweet scraper runs Twitter scraping through documented extraction jobs. Use `reply_extractor` as a Twitter reply scraper for visible reply authors. Use `follower_explorer` as a Twitter follower scraper for profile rows. The follower export API returns CSV, JSON, or XLSX files. Each job returns structured data for tweets, replies, followers, and following. Other jobs cover communities, lists, likes, reposts, quotes, and media posts. Tweet search results can filter authors, dates, engagement, media, relationships, lists, and locations.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
## Workflow overview | Extraction stage | Exact API call | Durable checkpoint | | ---------------- | ------------------------------ | -------------------------------------------------------- | | Check credits | `GET /account` | Available balance | | Estimate results | `POST /extractions/estimate` | `allowed`, `estimatedCost`, `estimatedResults`, `source` | | Start scraping | `POST /extractions` | `id`, `toolType`, `status` | | Retrieve rows | `GET /extractions/{id}` | `nextCursor` while `hasMore` is true | | Export a file | `GET /extractions/{id}/export` | Job ID, format, filename, destination | Treat `202 Accepted` as a queued run receipt. Credits are reserved after the job starts. Poll `GET /extractions/{id}` before handoff. The run can lower `resultsLimit` to the affordable count or fail with `insufficient_credits`. Verify that your available credit balance can cover the job. Preview the extraction cost before committing. Check whether it fits within your remaining budget. Submit the extraction job, store the `202 Accepted` receipt, then poll before handoff. Paginate through extracted data via the API, or export as CSV, JSON, XLSX, Markdown, PDF, or TXT. Download CSV, JSON, XLSX, Markdown, PDF, or TXT when a downstream tool expects a file. ## End-to-end agent handoff Store one checkpoint per extraction run so another worker can resume without rereading logs: ```json theme={null} { "workflow": "reply_extractor_to_csv", "request": { "toolType": "reply_extractor", "targetTweetId": "1893704267862470862", "resultsLimit": 500 }, "estimate": { "estimatedResults": 500, "creditsRequired": "500", "creditsAvailable": "77000", "allowed": true, "source": "replyCount" }, "create_receipt": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "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 }, "inventory_path": "/api/v1/extractions?status=completed&toolType=reply_extractor", "export_path": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=csv", "handoff_state": "poll_until_completed_then_export" } ``` Store `estimatedResults`, `creditsRequired`, `creditsAvailable`, `allowed`, and `source` before creating the job. Store the returned job `id`, `status`, and `poll_path`; result rows arrive from `GET /extractions/{id}`. Store `page_cursor`, `next_cursor`, and `has_more` for each JSON page so workers can resume pagination. Store `inventory_path` for later job lookup and `export_path` for the CSV, JSON, or XLSX download. ## Step 1: Check credits Before running an extraction, call [`GET /account`](/api-reference/account/get) and store a small planning checkpoint: ```bash cURL theme={null} curl -s https://xquik.com/api/v1/account \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```json theme={null} { "checkpoint_type": "credit_check", "plan": "active", "credit_balance": "77000", "next_action": "estimate_extraction" } ``` Store `plan` for billing context. Use `creditInfo.balance` to decide whether to continue, top up, or lower `resultsLimit` before estimating the extraction. ## Step 2: Estimate cost Use the estimate endpoint first. It shows expected rows and credits without charging you. ```bash cURL theme={null} 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 ``` ```javascript Node.js theme={null} const estimate = await fetch("https://xquik.com/api/v1/extractions/estimate", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ toolType: "reply_extractor", targetTweetId: "1893704267862470862", }), }).then((r) => r.json()); if (!estimate.allowed) { throw new Error(`Insufficient credits: need ${estimate.creditsRequired}, have ${estimate.creditsAvailable}`); } console.log(`Estimated results: ${estimate.estimatedResults}`); ``` ```python Python theme={null} estimate = requests.post( "https://xquik.com/api/v1/extractions/estimate", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "toolType": "reply_extractor", "targetTweetId": "1893704267862470862", }, ).json() if not estimate["allowed"]: raise Exception(f"Insufficient credits: need {estimate['creditsRequired']}, have {estimate['creditsAvailable']}") print(f"Estimated results: {estimate['estimatedResults']}") ``` **Response:** ```json theme={null} { "allowed": true, "creditsRequired": "150", "creditsAvailable": "77000", "estimatedResults": 150, "source": "replyCount", "resolvedXUserId": "44196397" } ``` `allowed` tells you whether the job can start with the current credit balance. `source` names the count used for the estimate, such as `replyCount`, `followers`, or `resultsLimit`. `estimatedResults` is the approximate number of records the job will return. `creditsRequired` is the projected credit usage for this extraction. `creditsAvailable` is your current balance when the estimate runs. `resolvedXUserId` appears when `targetUsername` resolves to an X user ID. If `allowed` is `false`, the extraction will return `402`. Top up credits or run a smaller job. Add `resultsLimit` to cap the number of results. Both the estimate and extraction endpoints accept this parameter. When `resultsLimit` is lower than the source estimate, the estimate uses `resultsLimit` as the projected count and returns `source: "resultsLimit"`. Otherwise it keeps the source count, such as `followers` or `replyCount`, and the extraction still stops once it reaches the cap. ## Step 3: Run the extraction Submit the job with the same parameters you used for the estimate. ```bash cURL theme={null} 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" }' | jq ``` ```javascript Node.js theme={null} const extraction = await fetch("https://xquik.com/api/v1/extractions", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ toolType: "reply_extractor", targetTweetId: "1893704267862470862", }), }).then((r) => r.json()); console.log(`Job ${extraction.id}: ${extraction.status}`); ``` ```python Python theme={null} extraction = requests.post( "https://xquik.com/api/v1/extractions", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "toolType": "reply_extractor", "targetTweetId": "1893704267862470862", }, ).json() print(f"Job {extraction['id']}: {extraction['status']}") ``` **Response:** ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "toolType": "reply_extractor", "status": "running" } ``` The endpoint returns `202 Accepted` with the job in `running` status. ## Step 4: Retrieve results Poll the extraction ID until it completes or fails. Then download a file or retrieve every JSON page. Save ID, row count, cursor, and format before loading. ## Data handoff Choose the handoff based on the system that consumes the extraction. Use `GET /extractions/{id}`. Store `job`, `results`, `hasMore`, `nextCursor`. Use `limit` up to 1,000 and pass `nextCursor` as `cursor`. Use `GET /extractions/{id}/export?format=csv`. Store `User ID`, `Username`, `Display Name`, `Followers`, and `Verified`. Upsert by stable X user ID when possible. Use `GET /extractions/{id}/export?format=json` or paginated JSON. Store `xUserId`, `xUsername`, `tweetId`, `tweetText`, `createdAt`. Keep the extraction ID with each load for replay and audit. Use `GET /extractions/{id}/export?format=xlsx`. Store export columns plus enrichment fields. Use CSV/JSON for automation and XLSX for manual review. Paginated JSON is not row-capped by the export limit. File exports are capped at 100,000 rows, and PDF exports are capped at 10,000 rows. ### Option A: Paginate via API Fetch results in pages of up to 1,000 records. Use cursor-based pagination to iterate through all results. ```bash cURL theme={null} # First page curl -s "https://xquik.com/api/v1/extractions/77777?limit=1000" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq # Next page (use nextCursor from previous response) curl -s "https://xquik.com/api/v1/extractions/77777?limit=1000&cursor=990100" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const extractionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"; let cursor = undefined; const allResults = []; do { const params = new URLSearchParams({ limit: "1000" }); if (cursor) params.set("after", cursor); const data = await fetch( `https://xquik.com/api/v1/extractions/${extractionId}?${params}`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ).then((r) => r.json()); allResults.push(...data.results); cursor = data.hasMore ? data.nextCursor : undefined; } while (cursor); console.log(`Fetched ${allResults.length} results`); ``` ```python Python theme={null} extraction_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" all_results = [] cursor = None while True: params = {"limit": 1000} if cursor: params["after"] = cursor data = requests.get( f"https://xquik.com/api/v1/extractions/{extraction_id}", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params=params, ).json() all_results.extend(data["results"]) if data.get("hasMore"): cursor = data["nextCursor"] else: break print(f"Fetched {len(all_results)} results") ``` ```go Go theme={null} extractionID := "a1b2c3d4-e5f6-7890-abcd-ef1234567890" cursor := "" var allResults []map[string]interface{} for { url := fmt.Sprintf("https://xquik.com/api/v1/extractions/%s?limit=1000", extractionID) if cursor != "" { url += "&cursor=" + cursor } req, _ := http.NewRequest("GET", url, nil) req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, _ := http.DefaultClient.Do(req) var data struct { Results []map[string]interface{} `json:"results"` HasMore bool `json:"hasMore"` NextCursor string `json:"nextCursor"` } json.NewDecoder(resp.Body).Decode(&data) resp.Body.Close() allResults = append(allResults, data.Results...) if !data.HasMore { break } cursor = data.NextCursor } fmt.Printf("Fetched %d results\n", len(allResults)) ``` ### Durable JSON Lines handoff Use JSON Lines when a queue, warehouse, or agent needs replayable rows without the export row cap. Write each paginated result with the cursor state that produced it. ```json theme={null} { "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "row_id": "990001", "x_user_id": "44196397", "x_username": "elonmusk", "tweet_id": "1893710452812718080", "tweet_text": "This is a great thread, thanks for sharing.", "page_cursor": "990100", "next_cursor": "990200", "has_more": true, "handoff_format": "jsonl" } ``` Store rows in `xquik-extraction-results.jsonl` for queue replay, warehouse loads, or agent audits. Keep `page_cursor` and `next_cursor` so the job can resume from the last successful page. Each result contains user profile data and (for tweet-based tools) tweet data: ```json theme={null} { "id": "990001", "xUserId": "44196397", "xUsername": "elonmusk", "xDisplayName": "Elon Musk", "xFollowersCount": 210500000, "xVerified": true, "xProfileImageUrl": "https://pbs.twimg.com/profile_images/el0n.jpg", "tweetId": "1893710452812718080", "tweetText": "This is a great thread, thanks for sharing.", "tweetCreatedAt": "2026-02-24T10:05:00.000Z", "createdAt": "2026-02-24T10:06:12.000Z" } ``` Only `id`, `xUserId`, and `createdAt` are guaranteed on every result. All other fields are omitted when unavailable (never `null`). Check for field presence before accessing. ### Option B: Export as file Download results as CSV, JSON, XLSX, Markdown, PDF, or TXT. Available export rows include bios, locations, and engagement counts. ```bash cURL theme={null} curl -s "https://xquik.com/api/v1/extractions/77777/export?format=csv" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -o extraction-reply_extractor-77777.csv ``` ```javascript Node.js theme={null} const extractionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"; const response = await fetch( `https://xquik.com/api/v1/extractions/${extractionId}/export?format=csv`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ); const blob = await response.blob(); // Save to file or process as needed ``` ```python Python theme={null} response = requests.get( f"https://xquik.com/api/v1/extractions/{extraction_id}/export", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"format": "csv"}, ) with open("extraction-reply_extractor-77777.csv", "wb") as f: f.write(response.content) ``` ```go Go theme={null} req, _ := http.NewRequest("GET", "https://xquik.com/api/v1/extractions/77777/export?format=csv", nil) req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() file, _ := os.Create("extraction-reply_extractor-77777.csv") defer file.Close() io.Copy(file, resp.Body) ``` `format=csv` returns `text/csv; charset=utf-8` for spreadsheets and data pipelines. `format=json` returns `application/json; charset=utf-8` for API clients and programmatic processing. `format=md` returns `text/markdown; charset=utf-8` for documentation and issue handoffs. `format=md-document` returns `text/markdown; charset=utf-8` for longer markdown documents. `format=pdf` returns `application/pdf` for reports and sharing. PDF exports are capped at `10,000` rows. `format=txt` returns `text/plain; charset=utf-8` for plain text and logs. `format=xlsx` returns `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` for Excel and formatted reports. Exports are capped at 100,000 rows (10,000 for PDF). For larger extractions, use the paginated API to retrieve all results. ## Tool types reference All 23 extraction tools grouped by target type. Each requires a specific target field. ### Tweet-based tools Use `reply_extractor` with `targetTweetId` to extract users who replied to a tweet. Use `repost_extractor` with `targetTweetId` to extract users who reposted a tweet. Use `quote_extractor` with `targetTweetId` to extract users who quote-posted a tweet. Use `favoriters` with `targetTweetId` to extract visible users who liked a post. Liker identities can be unavailable even when the post reports likes. Use `thread_extractor` with `targetTweetId` to extract all tweets in a thread. Use `article_extractor` with `targetTweetId` to extract article content from a tweet. ### User-based tools Use `follower_explorer` with `targetUsername` to extract followers of an account. Use `following_explorer` with `targetUsername` to extract accounts followed by a user. Use `verified_follower_explorer` with `targetUsername` to extract verified followers of an account. Use `mention_extractor` with `targetUsername` to extract tweets mentioning an account. Use `post_extractor` with `targetUsername` to extract posts from an account. Use `user_likes` with `targetUsername` to extract tweets liked by a user. Use `user_media` with `targetUsername` to extract media posts from a user. ### Community tools Use `community_extractor` with `targetCommunityId` to extract members of a community. Use `community_moderator_explorer` with `targetCommunityId` to extract moderators of a community. Use `community_post_extractor` with `targetCommunityId` to extract posts from a community. Use `community_search` with both `targetCommunityId` and `searchQuery` to search matching posts inside one community. ### List tools Use `list_member_extractor` with `targetListId` to extract members of a list. Use `list_post_extractor` with `targetListId` to extract tweets from a list. Use `list_follower_explorer` with `targetListId` to extract followers of a list. ### Other tools Use `people_search` with `searchQuery` to find user profiles by keyword. Use `space_explorer` with `targetSpaceId` to extract participants of a Space. Use `tweet_search_extractor` with `searchQuery` to extract tweets by keyword, hashtag, or structured filters. ### Tweet search filters `tweet_search_extractor` supports 31 optional filter parameters. Xquik converts them to X search operators internally. Other tool types ignore these filters. Use structured fields for authors, replies, media, dates, engagement, and locations. Use `advancedQuery` only for a known X search operator string. Use `fromUser` for author username, `toUser` for tweets directed to a user, and `mentioning` for tweets that mention a user. Use `language` for a language code such as `en`, `tr`, or `es`, then bound the export with `sinceDate` and `untilDate` in `YYYY-MM-DD` format. Use `mediaType` for `images`, `videos`, `gifs`, `media`, `links`, or `none`. Set `minFaves`, `minRetweets`, `minReplies`, or `minQuotes`. Use `replies`, `retweets`, and `quotes` with `include`, `exclude`, or `only`. Set `verifiedOnly` when only verified authors should match. Combine `exactPhrase`, `excludeWords`, `anyWords`, `hashtags`, `cashtags`, and `url` for terms, entities, or linked domains. Use `conversationId`, `inReplyToTweetId`, `quotesOfTweetId`, or `retweetsOfTweetId` for one conversation or source tweet. Use `listId`, `place`, `placeCountry`, `pointRadius`, or `boundingBox`. These fields narrow search results to a list or location. Use `advancedQuery` only for raw X search syntax. Prefer the 30 structured fields when one already represents the filter. **Example: Search for popular English tweets with images from the last week** ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/extractions \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "toolType": "tweet_search_extractor", "searchQuery": "artificial intelligence", "language": "en", "mediaType": "images", "minFaves": 100, "sinceDate": "2026-03-06", "untilDate": "2026-03-13", "retweets": "exclude" }' | jq ``` ```javascript Node.js theme={null} const extraction = await fetch("https://xquik.com/api/v1/extractions", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ toolType: "tweet_search_extractor", searchQuery: "artificial intelligence", language: "en", mediaType: "images", minFaves: 100, sinceDate: "2026-03-06", untilDate: "2026-03-13", retweets: "exclude", }), }).then((r) => r.json()); console.log(`Job ${extraction.id}: ${extraction.status}`); ``` Combine filters with `resultsLimit` to run targeted, cost-efficient searches. For example, find the top 50 viral tweets about a topic from verified accounts in the last 24 hours. ## Twitter Scraper API Questions ### What Is a Twitter Scraper API? A Twitter scraper API collects X posts and profiles through documented requests. Xquik creates a job, returns its ID, and exposes cursor-based JSON results. It can also generate downloadable files. This Twitter data scraper covers tweets, replies, followers, following, communities, and lists. It also covers spaces, likes, reposts, quotes, and media posts. ### Can Python Scrape Twitter Without the Official API? Yes, through Xquik's REST API and the Python examples above. API access still requires an [Xquik API key](/x-api-quickstart). You do not need to maintain an X developer application. Poll the job, follow every cursor, and save structured tweet or profile rows. ### How Do I Export Twitter Followers? Run `follower_explorer` with the Twitter account username in `targetUsername`. This Twitter follower scraper returns visible follower profiles. Estimate the job before creating it. Then poll every JSON page or download a file. The follower export API preserves each Twitter profile ID, username, counts, and verification. It also preserves names when available. ### How Do I Export Tweet Replies? Use `reply_extractor` with `targetTweetId` for visible reply-author profiles. Use `tweet_search_extractor` when the reply posts themselves matter. Pass `inReplyToTweetId` to restrict results to one source tweet. A tweet replies export can use CSV, JSON, XLSX, or paginated JSON. Store the source tweet ID, extraction ID, row IDs, and cursor checkpoint. ### Why Use a Twitter Scrape API Instead of a Custom Web Scraper? A custom web scraper must maintain selectors, login behavior, retries, and parsers. A Twitter scrape API uses documented requests, statuses, cursors, and output fields. Choose the API when stable CRM, warehouse, or agent handoffs matter. Choose custom code only when your team can maintain its collection logic. ## MCP equivalent The same workflow works through the MCP server using the `xquik` sandbox tool: REST route: `GET /account`. MCP call: `xquik.request('/api/v1/account')` to confirm account state, credits, and usage before a run. REST route: `POST /extractions/estimate`. MCP call: `xquik.request('/api/v1/extractions/estimate', { method: 'POST', body })` with the same body you plan to run. REST route: `POST /extractions`. MCP call: `xquik.request('/api/v1/extractions', { method: 'POST', body })` to create the background job. REST route: `GET /extractions/{id}`. MCP call: `xquik.request('/api/v1/extractions/ID')` to retrieve the job status and rows. **Example prompts for AI agents:** * "How much would it cost to extract all followers of @elonmusk?" * "Extract visible replies to this tweet: `https://x.com/vercel/status/1893704267862470862`" * "Show me the results of my last extraction." File export (`GET /extractions/{id}/export`) is only available via the REST API. The MCP server returns results as structured JSON via `xquik.request()`. ## Error handling A `402` means the account cannot fund this extraction. Check the available balance and `payment_options`. An active plan is not required when enough credits remain. `402 insufficient_credits` means the account cannot cover the requested extraction. Top up credits, wait for the next grant, or lower `resultsLimit`. `400 invalid_tool_type` means `toolType` is not one of the supported extraction tools. Check the [tool types](#tool-types-reference) reference. `400 invalid_input` usually means the target field is missing or malformed. Match `targetTweetId`, `targetUsername`, `targetCommunityId`, `targetListId`, `targetSpaceId`, or `searchQuery` to the selected tool. `502 x_api_unavailable` means the read service is temporarily unavailable. Retry with exponential backoff, then contact support if the error persists. ## Next steps Full API reference with request/response schemas. Cost estimation endpoint reference. CSV, XLSX, and Markdown export with column details. Pricing, credits, and usage scenarios. # Twitter Follower Scraper: Export Twitter Followers Source: https://docs.xquik.com/guides/follower-export-crm Download a Twitter follower list with usernames, profile URLs, bios, counts, and verification. Export CSV, XLSX, or JSON rows for CRM and warehouse imports.
For the complete documentation index, see llms.txt.
Download a Twitter follower list as CSV, XLSX, or JSON. Keep each Twitter profile ID, username, bio, verification status, image, and audience count. Import the follower rows into a CRM, spreadsheet, queue, or warehouse.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
## Twitter Follower Export Questions ### Download Follower List Twitter Run `follower_explorer` when you need a saved follower export. Use it as a Twitter follower tracker for repeatable account snapshots. Estimate credits before starting. Keep the source username and resolved user ID while polling the job to completion. Download CSV for CRM lists or spreadsheet imports. Choose XLSX for analyst review and JSON for queues. Use the user ID as each stored profile's deduplication key. Keep follower counts, following counts, verification, collection time, and the applied result limit. Mark any interrupted export as bounded. ### Export Twitter Followers API Use `POST /api/v1/extractions` for a saved follower export. Supply the public username and `follower_explorer`. A defined `resultsLimit` bounds each large export. Use `GET /api/v1/x/users/{id}/followers` for current profile pages. That route returns `users`, `has_next_page`, and `next_cursor`. Treat `next_cursor` as opaque. Persist every follower page before saving its cursor. Scope API access to the worker that owns each cursor. Keep the API key outside exported lists. Handle each documented rate limit and error status. Use the user ID to deduplicate retries. Store a timestamp for each collected follower page in its UTC collection record. ### What API Can I Use to Get Someone’s Twitter Followers? Resolve the public profile's stable ID before calling the [live Followers API contract](https://docs.xquik.com/api-reference/x/followers). Use `follower_explorer` for a saved export or list. A private account does not make its followers public. Confirm the Twitter profile is public before requesting follower pages. A missing page does not prove that a profile is private. Save each username, stable ID, and cursor. Record when each page was collected. Request another page only after the current profiles become durable. Respect API key scope, credits, and every returned rate limit. ### How Do I Export All Followers of a Twitter Account? Estimate the public profile first. Confirm credits and set `resultsLimit` before creating the follower export. Poll the job until completion or failure. Save each returned list page before its cursor. Deduplicate profiles by stable user ID after retries. Call the result a full list only when pagination ends normally. Label exports with fewer profiles as bounded. Record the source username, first cursor, final cursor, unique row count, and completion time. Follower totals may change during collection. ### Twitter Followers Scraper Choose the saved Twitter followers scraper for estimates and reusable files. Use it as a Twitter follower tracker for repeatable account snapshots. Choose the live follower API for low-latency profile pages. Each Twitter profile includes a user ID and username. It may include a bio and verification. It may also include follower counts, following counts, location, and profile images. Keep every API key scoped. Respect the read rate limit. Store cursors with their source profile. Never restart pagination under another username. ### How Can I Scrape Twitter Followers for a Specific Account? Supply one public username to `follower_explorer`. Estimate credits, create one job, and poll its returned ID. Export completed follower rows as CSV, JSON, or XLSX. Use the live API for current profile pages. Persist each list before its opaque cursor. Use numeric user IDs to deduplicate retries. Store the mutable username, bio, follower count, following count, and verification. Stop at a documented limit. Never infer private followers from an incomplete page. The final checkpoint records when the export completed. ### Export List of Twitter Followers to CSV or Excel Use `follower_explorer` when exporting a Twitter follower list. Choose CSV for CRM imports and Excel-compatible XLSX for analyst review. Choose JSON for queue, warehouse, or application ingestion. Map `User ID` to the CRM's unique profile field. Keep username, follower count, following count, verification, and bio as mutable values. Store the source account and extraction ID. Record every row cap in the file's import record. Save rejected import rows. Paginated JSON supports custom jobs that process large follower lists. ### Can I Export Twitter Followers to a Spreadsheet Automatically? Schedule estimate, create, poll, and export steps with an SDK or CLI. A no-code workflow must store the extraction ID. Use the [n8n workflow guide](https://docs.xquik.com/guides/n8n) for scheduled follower exports. Download CSV or XLSX for review. Keep each API key outside spreadsheet cells. Handle every returned rate limit. Never duplicate an uncertain create request. Store the source username, user ID, cursor, row cap, output path, and import result. Never assume a free tier. ### Can I Scrape Twitter Followers With Python Scripts? Use Python `requests` with documented Xquik follower endpoints. Call the live API when the script can persist every opaque cursor. Call extraction routes for saved CSV, JSON, or XLSX exports. Browser web scrapers may need cookies. Xquik operates independently from every official API documented by X. The official Twitter API and Xquik use separate contracts. Keep the API key outside source files. Check `402`, `424`, `429`, and `502` before retrying. Normalize each profile by its stable numeric ID. Write one follower per JSON Lines record. Store the username, list source, and UTC collection value. ### What Are the Risks of Twitter Follower Scraping Tools? Risks include partial follower pages, duplicate profiles, and exposed API keys. A repeated cursor can leave the follower list incomplete. Scraped data represents public follower profiles within the requested page boundaries. Never infer consent from a bio. Private accounts do not expose follower lists. Do not infer inactive accounts from missing pages or unchanged follower counts. Store each user ID beside its mutable username. Mark rate-limit, credit, or result boundaries. Review export columns before every CRM import. ### How Do I Get Public Follower Lists Responsibly? Public data includes documented profile fields from the requested public accounts. Keep only fields needed by the export's social media workflow. Review the [X Terms of Service](https://x.com/en/tos) at `https://x.com`. Follow applicable laws, retention rules, and deletion requests. Restrict API access and every follower export to its intended reviewers. Store the source profile, user ID, username, cursor, collection time, and documented boundary. This technical guide is not legal advice. Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. ### Can I Download My Own X Archive Instead? X calls its signed-in setting Download an archive of your data. Follow the [official X archive instructions](https://help.x.com/en/managing-your-account/how-to-download-your-x-archive) to create your account backup. The ZIP can include profile details, posts, media, followers, following, and Lists. It contains HTML and JSON, not a cursor-based API or CRM-ready CSV export. Use Xquik for repeatable public follower exports with defined row limits. Keep the source user ID and username. Private archives remain available only to their account owners. Record each downstream file's source. ## Outcome Call `POST /extractions/estimate` with `follower_explorer` to check credits and projected row count. Call `POST /extractions` with `targetUsername` and optional `resultsLimit` to start the async job. Call `GET /extractions/{id}` when a custom pipeline needs paginated JSON rows. Call `GET /extractions/{id}/export?format=csv`, `format=json`, or `format=xlsx` for CRM files; `md`, `md-document`, `pdf`, and `txt` are also supported. Exports are free and capped at 100,000 rows per extraction. PDF supports up to 10,000 rows. Map `User ID` to a CRM unique field for imports, upserts, segments, or warehouse loads. ## Choose the right path Use `POST /extractions/estimate`, then `POST /extractions` with `follower_explorer` when you need a reusable job ID, cost preview, and CSV/JSON/XLSX files. Use `GET /extractions/{id}?limit=1000&cursor={nextCursor}` for saved JSON pages. Use `GET /x/users/{id}/followers?pageSize=200&cursor={next_cursor}` when you need the current follower page without creating an extraction job. Use it for a CRM enrichment job, queue worker, audience sync, or agent. Use [CLI](/sdks/cli), [TypeScript](/sdks/typescript), [Python](/sdks/python), or [Go SDK](/sdks/go) when scheduled imports need code-owned JSON Lines, CSV, or XLSX files. ## End-to-end follower export handoff ```json theme={null} { "workflow": "follower_export_crm", "request": { "toolType": "follower_explorer", "targetUsername": "elonmusk", "resultsLimit": 10000 }, "estimate": { "estimatedResults": 10000, "creditsRequired": "10000", "creditsAvailable": "77000", "allowed": true, "source": "resultsLimit", "resolvedXUserId": "44196397" }, "create_receipt": { "id": "77777", "toolType": "follower_explorer", "status": "running", "poll_path": "/api/v1/extractions/77777" }, "json_pages": { "limit": 1000, "page_cursor": null, "next_cursor": "1001", "has_more": true }, "export_paths": { "csv": "/api/v1/extractions/77777/export?format=csv", "json": "/api/v1/extractions/77777/export?format=json", "xlsx": "/api/v1/extractions/77777/export?format=xlsx" }, "normalized_row": { "x_user_id": "44196397", "x_username": "username", "display_name": "Xquik", "followers_count": 2400, "verified": true, "profile_image_url": "https://pbs.twimg.com/profile_images/xquik.jpg", "bio": "X automation platform", "location": "San Francisco", "account_created_at": "2021-03-01T12:00:00.000Z" }, "crm_import": { "unique_field": "x_user_id", "upsert_mode": "external_id", "file_format": "csv", "file_path": "x-followers-elonmusk.csv" }, "handoff_state": "poll_until_completed_then_export" } ``` Keep `estimatedResults`, `creditsRequired`, `creditsAvailable`, `allowed`, `source`, and `resolvedXUserId`; `source` is `followers` unless `resultsLimit` is lower than the follower count. When the account has fewer followers than `resultsLimit`, the estimate keeps `source: "followers"` and uses the smaller follower count. Store the returned job `id`, `status`, and `poll_path`; do not expect follower rows in the create response. Store `extractionHandoff` in your job table. Poll by `extraction_id`. Keep `source_username` and `results_limit` for audit. Every result guarantees `id` and `xUserId`. The API omits optional profile and enrichment fields when absent. Use `limit` up to `1000`. Pass `nextCursor` as `cursor` until `hasMore` becomes `false`. Map `xUserId` to `x_user_id`. Store CSV, JSON, or XLSX `export_paths` and the CRM `unique_field`. Use the local `-o` path, `exportFilePath`, or `export_file_path` as the durable handoff value. ### Live follower page handoff ```bash cURL theme={null} curl "https://xquik.com/api/v1/x/users/44196397/followers?pageSize=200&cursor=DAACCgACGRElMJcAAA" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` The response returns `users`, `has_next_page`, and `next_cursor`. Treat `next_cursor` as opaque. Never treat `cursor` as a stable follower ID. Map `users[].id` to `x_user_id`, `users[].username` to `x_username`, and `users[].name` to `display_name`. Map optional `followers`, `following`, `statusesCount`, `verified`, and `verifiedType` for scoring or segments. Map optional `description`, `location`, `url`, `profilePicture`, and `coverPicture`. Map `users[].createdAt` to `account_created_at` when returned. Automatic `pageSize` accepts `20` to `300`. Standard accepts `20` to `200`. Credits can reduce the returned row count. Zero affordable results return `402 insufficient_credits`. ```json theme={null} { "job": "direct_follower_page", "source_x_user_id": "44196397", "page_size_requested": 200, "rows_returned": 200, "cursor_used": "DAACCgACGRElMJcAAA", "next_cursor": "DAACCgACGE...", "has_next_page": true, "fetched_at": "2026-05-16T16:50:00.000Z", "page_checkpoint": "44196397:DAACCgACGE..." } ``` ## Step 1: Estimate cost ```bash cURL theme={null} 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": "follower_explorer", "targetUsername": "elonmusk", "resultsLimit": 10000 }' | jq ``` ```javascript Node.js theme={null} const estimate = await fetch("https://xquik.com/api/v1/extractions/estimate", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ toolType: "follower_explorer", targetUsername: "elonmusk", resultsLimit: 10000, }), }).then((response) => response.json()); if (!estimate.allowed) { throw new Error( `Need ${estimate.creditsRequired} credits, have ${estimate.creditsAvailable}`, ); } ``` ```python Python theme={null} import requests estimate = requests.post( "https://xquik.com/api/v1/extractions/estimate", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, json={ "toolType": "follower_explorer", "targetUsername": "elonmusk", "resultsLimit": 10000, }, ).json() if not estimate["allowed"]: raise RuntimeError( f"Need {estimate['creditsRequired']} credits, have {estimate['creditsAvailable']}" ) ``` ```json theme={null} { "allowed": true, "creditsRequired": "10000", "creditsAvailable": "77000", "estimatedResults": 10000, "source": "resultsLimit", "resolvedXUserId": "44196397" } ``` ## Step 2: Start the extraction ```bash cURL theme={null} curl -X POST https://xquik.com/api/v1/extractions \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "toolType": "follower_explorer", "targetUsername": "elonmusk", "resultsLimit": 10000 }' | jq ``` ```javascript Node.js theme={null} const job = await fetch("https://xquik.com/api/v1/extractions", { method: "POST", headers: { "x-api-key": "xq_YOUR_KEY_HERE", "Content-Type": "application/json", }, body: JSON.stringify({ toolType: "follower_explorer", targetUsername: "elonmusk", resultsLimit: 10000, }), }).then((response) => response.json()); const extractionHandoff = { extraction_id: job.id, status: job.status, source_username: "elonmusk", results_limit: 10000, handoff_created_at: new Date().toISOString(), }; ``` ```json theme={null} { "id": "77777", "toolType": "follower_explorer", "status": "running" } ``` ## Step 3: Poll until complete ```bash cURL theme={null} curl -s "https://xquik.com/api/v1/extractions/77777?limit=1000" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ### Paginated JSON handoff ```json theme={null} { "job": { "id": "77777", "toolType": "follower_explorer", "status": "completed", "totalResults": 10000, "targetUsername": "elonmusk", "createdAt": "2026-05-08T13:46:00.000Z", "completedAt": "2026-05-08T13:51:00.000Z" }, "results": [ { "id": "1001", "xUserId": "44196397", "xUsername": "username", "xDisplayName": "Xquik", "xFollowersCount": 2400, "xVerified": true, "xProfileImageUrl": "https://pbs.twimg.com/profile_images/xquik.jpg", "enrichmentData": { "user": { "followingCount": 120, "statusesCount": 830, "description": "X automation platform", "location": "San Francisco", "coverPicture": "https://pbs.twimg.com/profile_banners/xquik" }, "tweet": {} }, "createdAt": "2026-05-08T13:51:00.000Z" } ], "hasMore": true, "nextCursor": "1001" } ``` Normalize each follower row before sending it downstream: ```json theme={null} { "job": "follower_export", "source_username": "elonmusk", "result_id": "1001", "x_user_id": "44196397", "x_username": "username", "display_name": "Xquik", "followers_count": 2400, "verified": true, "profile_image_url": "https://pbs.twimg.com/profile_images/xquik.jpg", "bio": "X automation platform", "location": "San Francisco", "handoff_format": "json" } ``` ## Step 4: Export Twitter Follower List in CSV Format ```bash cURL theme={null} curl -s "https://xquik.com/api/v1/extractions/77777/export?format=csv" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -o x-followers-elonmusk.csv ``` ```javascript Node.js theme={null} import { writeFile } from "node:fs/promises"; const extractionId = "77777"; const exportFilePath = "x-followers-elonmusk.csv"; const response = await fetch( `https://xquik.com/api/v1/extractions/${extractionId}/export?format=csv`, { headers: { "x-api-key": "xq_YOUR_KEY_HERE" } }, ); if (!response.ok) { throw new Error(`Export failed with ${response.status}`); } const bytes = Buffer.from(await response.arrayBuffer()); await writeFile(exportFilePath, bytes); const exportHandoff = { extraction_id: extractionId, source_username: "elonmusk", export_format: "csv", export_file_path: exportFilePath, content_disposition: response.headers.get("Content-Disposition") ?? "", handoff_created_at: new Date().toISOString(), }; ``` ```python Python theme={null} extraction_id = "77777" export_file_path = "x-followers-elonmusk.csv" response = requests.get( f"https://xquik.com/api/v1/extractions/{extraction_id}/export", headers={"x-api-key": "xq_YOUR_KEY_HERE"}, params={"format": "csv"}, ) response.raise_for_status() with open(export_file_path, "wb") as file: file.write(response.content) ``` ```json theme={null} { "job": "follower_export_file", "extraction_id": "77777", "source_username": "elonmusk", "export_format": "csv", "export_file_path": "x-followers-elonmusk.csv", "handoff_format": "file" } ``` ## CRM field mapping Map export column `User ID` to `x_user_id` as the custom unique field. Use it for CRM upserts because it is the stable X account identifier. Map `Username` to `x_username` or a social profile field, and map `Display Name` to `display_name` for triage and human review. Map `Followers` to `followers_count`, `Following` to `following_count`, and `Posts` to `posts_count` for segmentation, scoring, and reporting. Map `Verified` to `verified` so lead scoring, audience filters, and reviewer queues can separate verified accounts. Map `Description` to a bio or notes field, and map `Location` to a regional field. Treat both as mutable enrichment, not identity. Map `Profile Image` to an avatar URL field and `Cover Picture` to a banner URL field for internal views that show account context. ## Import and upsert rules Check file requirements, unique identifier rules, and supported spreadsheet formats before importing. Use CSV upserts with an external ID field when loading large contact or lead datasets. ## Automation handoff Use this handoff when the export runs on a schedule: 1. Store the Xquik extraction ID and source username in your job table. 2. Poll `GET /extractions/{id}` until the API returns `completed`. 3. Download `format=json` for code pipelines or `format=csv` for CRM import tools. 4. Upsert by `x_user_id`, not by display name or username. 5. Save the Xquik job ID, export format, export file path, row count, and import result for audit. 6. For JSON Lines, write one normalized follower object per line to `xquik-follower-export.jsonl`. ## Related Estimate costs, run jobs, paginate results, and export files. Fetch one live follower page with `users`, `has_next_page`, and `next_cursor`. Send extraction rows into Sheets, Slack, and downstream automation. Apply read rate limits to direct follower pages and retry `429` responses. # X API Glossary for Tweets, Followers & Webhooks Source: https://docs.xquik.com/guides/glossary Use this X API glossary to define tweets, replies, reposts, followers, profiles, media, cursors, monitors, webhooks, extractions, credits, and errors.
For the complete documentation index, see llms.txt.
Use this X API glossary while reading Xquik API responses and guides. It defines concrete tweet, profile, follower, monitor, webhook, and billing terms. X calls its content objects posts. Xquik keeps `tweet` in public route names. This preserves familiar Twitter API terms and existing client integrations. Treat IDs and cursors as strings. Treat counts as collection-time snapshots. Treat optional fields as absent unless the response supplies them. ## Quick X API Terminology | Term | Short definition | Primary reference | | ----------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | Tweet or post | One X content object with text and optional media. | [Get tweet](/api-reference/x/get-tweet) | | Reply | A tweet linked to a direct parent and conversation. | [Tweet replies](/api-reference/x/tweet-replies) | | Quote tweet | A new tweet that embeds another tweet. | [Tweet quotes](/api-reference/x/tweet-quotes) | | Repost or retweet | A redistribution of an existing tweet. | [Retweeters](/api-reference/x/retweeters) | | Profile | An X account's identity, biography, audience, and status fields. | [Get user](/api-reference/x/twitter-profile-lookup) | | Followers | Accounts that follow the source profile. | [Followers](/api-reference/x/followers) | | Following | Accounts the source profile follows. | [Following](/api-reference/x/following) | | Cursor | An opaque token for the next result page. | [Request-efficient usage](/guides/request-efficient-api-usage#store-cursor-checkpoints) | | Extraction | A durable tweet, reply, profile, or audience export job. | [Extraction workflow](/guides/extraction-workflow) | | Monitor | A recurring account or keyword check. | [Create monitor](/api-reference/monitors/create) | | Event | A stored tweet or profile change detected by a monitor. | [List events](/api-reference/events/list) | | Webhook | A signed HTTP callback containing one monitor event. | [Webhooks](/webhooks/overview) | | Write action | A connected-account operation that changes X state. | [Create tweet](/api-reference/x-write/create-tweet) | | Credit | The shared metered unit used by Xquik operations. | [Pricing & billing](/guides/billing) | | Rate limit | A request-frequency boundary for one account bucket. | [Rate limits](/guides/rate-limits) | ## Tweet, Reply & Media Terms One content object published on X. Xquik routes use the word `tweet`. A tweet can contain text, media, links, mentions, hashtags, or polls. Store its `id` as a string. See the [tweet field guide](/guides/tweet-profile-api-fields#tweet-fields). The stable string identifier for one tweet. A status URL usually ends with `/status/{tweetId}`. Use the ID for joins, dedupe, replies, quotes, likes, reposts, and media lookups. Never store it as a JavaScript number. A tweet written in response to another tweet. `inReplyToId` identifies the direct parent. `conversationId` identifies the root conversation. A reply remains its own tweet with its own author and engagement counts. A new tweet that adds text while referencing another tweet. The outer tweet owns the quote text. `quoted_tweet` contains the referenced tweet when available. Keep both tweet IDs and metric snapshots separate. A redistribution of an existing tweet. X now uses the term repost. Xquik retains `retweet` in route and field names for compatibility. `retweeted_tweet` contains original tweet context when available. A thread is a connected sequence of tweets and replies. Use `conversationId` to group a conversation. Use `inReplyToId` to preserve each direct parent edge. Do not infer reply order from IDs alone. A reference to an X username inside tweet text. Mentions can appear in parsed `entities` and monitor events. A mention does not prove a reply, follow, endorsement, or conversation relationship. A hashtag begins with `#`. A cashtag begins with `$`. Both can appear inside tweet entities and search queries. Preserve the original token before applying lowercase normalization. Parsed tweet markers for URLs, mentions, hashtags, and related tokens. Entity arrays are conditional. Preserve the original tweet text beside extracted tokens. See the [official X data dictionary](https://docs.x.com/x-api/fundamentals/data-dictionary) for upstream object terminology. An image, video, or animated GIF attached to a tweet or message. Xquik can return media URLs, types, dimensions, preview images, variants, alt text, and identifiers. Availability depends on the returned object. Numeric snapshots for likes, replies, reposts, quotes, views, and bookmarks. Counts can change after collection. Store `collected_at` with every metrics snapshot. A missing count is not automatically zero. A Note Tweet carries long-form tweet content and metadata. An X Article is a separate long-form object referenced from a tweet. Preserve returned nested objects instead of flattening their text into one field. ## Profile, Follower & Audience Terms The account object behind a username. Core fields include `id`, `username`, and `name`. Optional fields can include a biography, images, verification, location, followers, following, activity counts, and availability state. The stable string identifier for one X account. Prefer it for database joins. A username can change. A user ID remains the stronger profile key. The public account name used after `@`. Xquik accepts usernames with or without a leading `@` on supported routes. Store the returned user ID too. Accounts that follow the source profile. `GET /x/users/{id}/followers` returns follower profiles and cursor state. A follower snapshot can change after collection. Store the source user ID and collection time. Accounts the source profile follows. `GET /x/users/{id}/following` returns those profiles. Following is directional. It does not prove mutual follow. A directional edge between two X accounts. Use [`GET /x/followers/check`](/api-reference/x/check-follower) when one current relationship matters. Use follower or following pages for audience exports. An account whose posts have restricted visibility. A public profile lookup can still return limited metadata. Do not claim complete tweet or follower coverage when the response marks an account protected or unavailable. An ordered page of tweets. A user timeline contains one profile's tweets. A home timeline reflects the connected account's feed. Save each returned cursor before requesting the next page. An X group with members, moderators, rules, and a community tweet timeline. Xquik exposes community details, members, moderators, tweets, and search. A curated X account collection. List members belong to the collection. List followers follow the list itself. These are different profile sets. A private message between X accounts. DM history and writes require a connected X account. Store message IDs, participant IDs, timestamps, and returned media references. ## Twitter Scraper API & Export Terms An API that retrieves tweets, replies, profiles, followers, following, timelines, communities, lists, or media as structured responses. Xquik exposes these reads through REST, SDKs, MCP, and Actors. A query over recent or top tweets. Search can use keywords, hashtags, authors, dates, media filters, language, and engagement filters. Preserve the exact query and filters across cursor pages. One bounded response from a list endpoint. A requested page size is an upper bound. Fewer rows do not prove pagination finished. Check the returned continuation flag and cursor. Pagination using opaque cursor tokens instead of page numbers. For X reads, pass `next_cursor` back as `cursor`. Events, draws, and extractions use `cursor`. Radar uses `after`. Drafts use `afterCursor`. Never decode or modify a cursor. One request containing multiple tweet or profile IDs. Batch routes reduce repeated HTTP calls. Each returned object still needs its own ID-based validation and error handling. A durable job for collecting and saving tweet, reply, profile, follower, following, community, list, media, or article results. Store the extraction ID. Poll status before retrieving JSON pages or file exports. A saved CSV, JSON, or XLSX representation of extraction results. Direct API pages can also feed JSONL or database rows. Record the query, final cursor, row count, format, and completion state. A collection-time view of profiles, relationships, or engagement counts. Followers, following, biographies, and tweet metrics can change later. Store collection timestamps before comparing two snapshots. Removing repeated objects with a stable ID. Dedupe tweets by tweet ID. Dedupe profiles by user ID. Keep the newest complete optional fields when merging repeated pages or overlapping date windows. ## Monitor, Event & Webhook Terms A recurring account or keyword check. Monitor slots are unlimited. Active monitors check every second and cost 21 credits per hour. Account monitors track one username. Keyword monitors track a search query. A stored tweet or profile change detected by a monitor. Account monitors support 21 event types. Keyword monitors support the 10 `tweet.*` types. Account monitors do not emit follower-gained or follower-lost events. A monitor signal such as `tweet.new`, `tweet.reply`, `tweet.quote`, `tweet.retweet`, `tweet.media`, `tweet.link`, or `tweet.poll`. Subscribe only to types your receiver handles. A detected profile change. Events cover names, usernames, biographies, locations, URLs, avatars, banners, verification, protection, pinned tweets, and availability. Keyword monitors do not accept profile event types. An HTTPS callback for one monitor event. Xquik signs webhook requests with HMAC-SHA256. Verify `X-Xquik-Signature` and `X-Xquik-Timestamp` before processing the JSON body. The identifier for one webhook delivery attempt sequence. Store `deliveryId` for receiver dedupe. Repeated delivery attempts must not create repeated tickets, alerts, or warehouse rows. The stable ID of the stored monitor event behind a webhook payload. Join `streamEventId` to the events API when reconciling webhook delivery with stored tweet or profile changes. Replay means processing a previously stored event again. Reconciliation compares webhook receipts with the events API. Use event IDs, delivery IDs, and cursors to find missing or duplicate work. A giveaway winner selection from one tweet's eligible participants. Draws can filter replies, follower counts, account age, reposts, following, language, keywords, hashtags, and mentions. Results include an audit page. ## X Write & Authentication Terms An X account authorized for private reads or write actions. Public tweet, profile, follower, reply, community, and list reads need no connected account. Tweets, likes, follows, DMs, and profile changes require one. An operation that changes X state. Examples include creating or deleting tweets, liking, reposting, following, sending DMs, and updating profiles. Write costs vary by operation and media size. The persisted lifecycle record for an X write. It records request identity, target, billing, status, retry safety, and next action. Store the returned durable action before starting another write. A unique request key that prevents accidental duplicate write submission. Reuse the key only for the identical request. Use a new key only when the durable action says `safeToRetry` is `true`. `statusUrl` identifies the durable action to poll. `terminal` shows whether processing ended. `safeToRetry` shows whether another submission is safe. Poll an active action instead of resending it. Authentication credential passed via the `x-api-key` header. Account keys also support Bearer authentication. Generate account keys from the [API Keys dashboard](https://dashboard.xquik.com/en/account?tab=api-keys) or the session-authenticated [API keys endpoint](/api-reference/api-keys/create). Scoped authorization for account access without sharing an API key. Xquik uses S256 PKCE for authorization-code flows. Preserve granted scopes. Use refresh-token grants only through the documented OAuth endpoints. The machine-readable specification for public routes, parameters, schemas, responses, and examples. Use `https://docs.xquik.com/openapi.yaml` as the public REST contract. Never infer undocumented response statuses. An opt-in response shape selected with `xquik-api-contract: 2026-04-29`. It uses snake-case envelopes, prefixed resource IDs, structured errors, and normalized pagination fields. ## Billing, Error & Rate-Limit Terms The metered unit for API usage. Costs depend on the operation, returned results, active monitor hours, write type, and media size. Subscription grants, top-ups, and automatic top-ups add credits to one shared balance. Unused credits carry over until you spend them. An operation that deducts credits. Paid tweet, profile, follower, reply, timeline, extraction, draw, media, write, and active-monitor work can be metered. Check each route's cost callout before running it. An operation that consumes no credits. Stored monitor management, stored event reads, webhook operations, saved extraction reads, exports, account status, and API-key management are free. Active monitors are billed hourly. Pay-as-you-go credits purchased by top-up. Top-up credits cost USD 0.00015 each. Top-up credits are added to your balance immediately and do not expire. An accountless prepaid balance for 33 eligible GET routes. A verified hosted payment activates its Bearer key. Guest keys cannot access writes, account management, or private X reads. Machine Payments Protocol for direct per-call payment. Seven fixed-price read operations accept an MPP Payment challenge without a subscription. A failed request does not create checkout automatically. The caller cannot fund a metered operation. Account and guest responses can include credential-specific `payment_options`. Confirm any purchase before creating checkout or charging a saved payment method. The caller exceeded a request bucket or action cooldown. Read `error` and `Retry-After` before retrying. A `429` is not a credit failure. Xquik's rate-limiting algorithm. Standard accounts receive independent read, write, and delete counters. Reads reset after 1 second. Writes and deletes reset after 60 seconds. See [rate limits](/guides/rate-limits). A response header containing seconds to wait. It can accompany a `429`, active write, connection cooldown, or temporary service response. Inspect the status and error code before choosing an action. ## Common Twitter API Definition Questions ### What Is the Difference Between a Tweet and a Post? They describe the same core X content object. X now says post. Xquik keeps tweet terminology in routes, fields, and developer documentation. ### What Is the Difference Between Followers and Following? Followers are accounts that follow the source profile. Following are accounts the source profile follows. Both relationships are directional. ### How Do Replies, Quotes & Reposts Differ? A reply points to a parent tweet. A quote adds new text around another tweet. A repost redistributes the original. Keep every involved tweet ID separate. ### Is Tweet Scraping the Same as Monitoring? No. A scraper API retrieves requested tweets, profiles, or audience pages. A monitor runs repeatedly and stores matching tweet or profile events. ### Why Does Xquik Use Cursors Instead of Page Numbers? Cursors preserve server-defined continuation state for changing result sets. Pass them back unchanged. Never calculate or decode the next cursor. ### Is a Webhook the Same as Polling? No. A webhook pushes signed monitor events to your HTTPS receiver. Polling repeatedly requests events. Use polling for replay and reconciliation. ### Is an API Key the Same as a Connected X Account? No. An API key authenticates the Xquik caller. A connected X account authorizes private reads and X write actions. ### What Is the Difference Between 402 and 429? `402` means the request lacks sufficient payment or credits. `429` means the request rate or action frequency was exceeded. Map tweet, profile, media, relationship, and cursor fields. Choose tweet, follower, reply, monitor, webhook, and write routes. Classify validation, billing, rate-limit, dependency, and write errors. # Google ADK MCP Twitter API Tutorial for Python Source: https://docs.xquik.com/guides/google-adk Build Google ADK tools for tweet search, followers, monitors, and approved X actions. Follow a tested Python MCP server guide with typed handoffs and retries.
For the complete documentation index, see llms.txt.
This Google ADK tutorial uses Xquik's remote MCP server. Build a Google ADK Twitter API agent through Xquik using this architecture. This Twitter API for Python workflow searches tweets, profiles, and followers. It also replays monitors and reviews approved X actions. The handoff must preserve tweet IDs, profile IDs, cursors, and job IDs. ## Why Use Google ADK With a Twitter API? Google's Agent Development Kit (ADK) manages Gemini calls, tools, sessions, and agent handoffs. Model Context Protocol, or MCP, defines interoperable tool schemas. Xquik exposes both `explore` and `xquik` as remote MCP tools. This Google ADK guide assigns one responsibility to each boundary. The Agent Development Kit MCP client loads Xquik route schemas during startup. | Boundary | Google ADK control | Twitter agent benefit | | ------------------ | --------------------------------------------- | -------------------------------------------------------------- | | Remote MCP | `McpToolset` | Connect Gemini to tweet, profile, follower, and monitor routes | | Final response | Pydantic `output_schema` | Reject malformed tweet rows and pagination state | | Session continuity | `InMemoryRunner` or a durable session service | Resume with the same tweet IDs and cursors | | Agent handoff | `output_key` and sub-agents | Separate research, review, and publishing | | X actions | `require_confirmation` | Pause before the `xquik` execution tool runs | | Tenant credentials | `header_provider` | Resolve one Xquik key for each tenant | Choose ADK when Gemini already powers the surrounding workflow. Choose a direct REST client for deterministic jobs without model decisions. ## Google ADK Twitter API Prerequisites * Python 3.10 or later * An [Xquik API key](/x-api-quickstart) beginning with `xq_` * A Google AI API key for Gemini * A connected X account for private reads or X write actions Public X reads do not require X Developer credentials. Authenticate with Xquik. Connect an X account only when the selected route requires it. ## Install Google ADK Python and MCP Support Install the current Google ADK 2.6 line with its compatible MCP client. ```bash theme={null} python -m pip install --upgrade \ "google-adk[mcp]>=2.6,<2.7" \ "pydantic>=2.12,<3" \ python-dotenv ``` Google ADK 2.6 requires MCP 1.x. Its `mcp` extra excludes MCP 2.x. Store secrets outside source control. ```bash .env theme={null} XQUIK_API_KEY=xq_YOUR_KEY_HERE GOOGLE_API_KEY=YOUR_GOOGLE_AI_KEY ``` ```text .gitignore theme={null} .env xquik-*-handoff.json ``` The environment variables isolate Xquik and Google credentials from the agent implementation. Never place either key inside an agent instruction. ## Connect Google Agent Development Kit to an MCP Server This Google Agent Development Kit MCP server setup uses Xquik remotely. ADK serves as the MCP client, while Xquik hosts the remote server. `McpToolset` discovers `explore` and `xquik` through standard tool calling. These Google ADK tools cover tweets, profiles, followers, monitors, and X actions. The separation keeps agent workflows independent from route schemas. ## Build a Google ADK Python Tweet Search Agent Define the final handoff before creating the agent. ADK validates the final Gemini response while still allowing MCP tool calls. `from google.adk import Agent` imports the current agent class. This Google ADK Python example validates final responses with Pydantic. The Twitter API get tweets workflow calls the documented search route. ```python theme={null} import asyncio import os from pathlib import Path from typing import Literal from dotenv import load_dotenv from google.adk import Agent from google.adk.runners import InMemoryRunner from google.adk.tools.mcp_tool.mcp_session_manager import ( StreamableHTTPConnectionParams, ) from google.adk.tools.mcp_tool.mcp_toolset import McpToolset from google.genai import types from pydantic import BaseModel class TweetRow(BaseModel): tweet_id: str text: str author_username: str | None = None created: int | None = None url: str | None = None class TweetSearchHandoff(BaseModel): query: str route_used: str tweets: list[TweetRow] has_more: bool next_cursor: str | None = None pages_fetched: int stop_reason: Literal["complete", "requested_limit", "cursor_stalled"] async def main() -> None: load_dotenv() xquik_tools = McpToolset( connection_params=StreamableHTTPConnectionParams( url="https://xquik.com/mcp", headers={"x-api-key": os.environ["XQUIK_API_KEY"]}, timeout=15, sse_read_timeout=120, ), tool_filter=["explore", "xquik"], ) agent = Agent( model="gemini-3.5-flash", name="xquik_tweet_search_agent", instruction=( "Use Xquik for Twitter API requests. " "Use GET /api/v1/x/tweets/search. " "Preserve exact tweet IDs, created timestamps, and cursors. " "Never invent missing tweet fields. " "Stop when the requested limit is reached. " "Stop if the server repeats a cursor." ), tools=[xquik_tools], output_schema=TweetSearchHandoff, ) async with InMemoryRunner( agent=agent, app_name="xquik_adk_app", ) as runner: session = await runner.session_service.create_session( app_name="xquik_adk_app", user_id="user-1", ) final_text = "" async for event in runner.run_async( user_id="user-1", session_id=session.id, new_message=types.Content( role="user", parts=[ types.Part( text=( "Search 50 latest tweets about Google ADK MCP. " "Return compact validated JSON." ) ) ], ), ): if ( event.is_final_response() and event.content and event.content.parts ): final_text = "".join( part.text or "" for part in event.content.parts ) handoff = TweetSearchHandoff.model_validate_json(final_text) Path("xquik-adk-handoff.json").write_text( handoff.model_dump_json(indent=2), encoding="utf-8", ) asyncio.run(main()) ``` The runner closes `McpToolset` when its context exits. Reuse one runner across related turns. Repeated setup adds avoidable MCP handshakes. `xquik.request()` returns normalized snake\_case fields from the MCP runtime. It normalizes `createdAt` to the Unix-second field `created`. Align the Pydantic schema with this response format. ## Search Tweets With Useful X Operators Send the search route a focused `q`. Keep the exact query in the handoff. | Search intent | Example `q` | | ----------------------------- | ---------------------------------------------------- | | Latest topic tweets | `"Google ADK" MCP` | | One account's timeline window | `from:googlecloud since:2026-07-01 until:2026-08-01` | | Popular English posts | `"agent development kit" lang:en min_faves:25` | | Questions without reposts | `"Twitter API agent" ? -filter:retweets` | | Exact tweet | A numeric Tweet ID or X status URL | This Twitter API integration accepts standard X search operators. Use `queryType=Latest` for chronological monitoring. Use `Top` for engagement ranking. See the complete [tweet search API contract](/api-reference/x/search-tweets). The search query `twitter api get tweets` maps to this route. Pass the returned `next_cursor` unchanged. Never reconstruct a cursor. Stop when `has_more` is false, the requested limit is met, or a cursor repeats. ## Separate Tweet Research From X Actions Xquik intentionally exposes one `xquik` execution tool. MCP tool-name filters cannot distinguish GET, POST, and DELETE requests inside that tool. This Google ADK multi agent pattern separates research from publishing. Use separate credentials and confirmation rules across multi agent systems. Use separate ADK agents. Let the research agent read tweets and profiles. Make the publishing agent confirm every `xquik` execution. ```python theme={null} import os from google.adk import Agent from google.adk.tools.mcp_tool.mcp_session_manager import ( StreamableHTTPConnectionParams, ) from google.adk.tools.mcp_tool.mcp_toolset import McpToolset def connection() -> StreamableHTTPConnectionParams: return StreamableHTTPConnectionParams( url="https://xquik.com/mcp", headers={"x-api-key": os.environ["XQUIK_API_KEY"]}, ) research_tools = McpToolset( connection_params=connection(), tool_filter=["explore", "xquik"], ) approved_write_tools = McpToolset( connection_params=connection(), tool_filter=["xquik"], require_confirmation=True, ) researcher = Agent( name="tweet_researcher", model="gemini-3.5-flash", instruction=( "Search tweets and profiles. Preserve IDs, created timestamps, " "has_more, and next_cursor. Never perform X write actions." ), tools=[research_tools], output_key="tweet_research", ) publisher = Agent( name="approved_x_publisher", model="gemini-3.5-flash", instruction=( "Use only reviewed tweet text and the selected X account. " "Never change approved text. Never retry a pending write." ), tools=[approved_write_tools], ) coordinator = Agent( name="twitter_campaign_coordinator", model="gemini-3.5-flash", instruction=( "Delegate tweet research to tweet_researcher. " "Delegate approved X actions to approved_x_publisher." ), sub_agents=[researcher, publisher], ) ``` ADK CLI and ADK Web can present confirmation requests. A custom interface must return ADK's confirmation `FunctionResponse`. Treat confirmation as a product workflow, not a prompt sentence. The safest writer confirms every `xquik` call. A code-string inspection is not a reliable authorization boundary. For public research, a guest `paid_reads` key allows eligible GET routes only. [Guest wallets](/guides/guest-wallets) document the exact scope. ## Discovery-Only Tool Filtering Expose only `explore` when an agent should inspect endpoint schemas. ```python theme={null} discovery_tools = McpToolset( connection_params=connection(), tool_filter=["explore"], ) ``` This setup blocks Twitter API execution. Adding `xquik` enables key-authorized reads and writes. Because `xquik` wraps methods, name filtering cannot enforce read-only access. Use `explore` before unfamiliar operations. It returns routes, methods, parameters, and response fields without calling X. ## Store Tweet IDs and Cursors in ADK State Keep compact identifiers in session state. Exclude complete tweet pages from ADK prompts and stored session state. Plain state keys belong to one conversation. `user:` keys persist across a user's sessions. `app:` keys hold shared configuration. `temp:` keys expire after one invocation. Recommended state entries include: * `query` and `query_type` * `last_tweet_id` and `selected_tweet_ids` * `next_cursor` and `pages_fetched` * `monitor_id` and `last_event_id` * `extraction_id`, `status`, and `poll` * `write_action_id`, `status`, and `charged_credits` Never store Xquik keys in ADK state. Let `header_provider` resolve credentials from your secret manager. ## Use Tenant-Specific Xquik API Keys `header_provider` runs when ADK opens the MCP session. Read a non-secret tenant identifier from `ReadonlyContext`. Resolve the key outside the prompt. ```python theme={null} from google.adk.agents.readonly_context import ReadonlyContext def tenant_headers(context: ReadonlyContext) -> dict[str, str]: tenant_id = str(context.state["tenant_id"]) api_key = secret_store.get_xquik_key(tenant_id) return {"x-api-key": api_key} tenant_tools = McpToolset( connection_params=StreamableHTTPConnectionParams( url="https://xquik.com/mcp", ), header_provider=tenant_headers, tool_filter=["explore", "xquik"], ) ``` `secret_store` represents an existing secret manager. Keep its returned keys outside Gemini prompts and ADK events. ## Handle Tweet Search Errors and Rate Limits Handle every documented status with its matching recovery step. | Status | Meaning | Agent action | | ------ | ------------------------------------------------- | ----------------------------------------------- | | `400` | The search query is missing or invalid | Fix `q`; do not retry unchanged | | `401` | Guest authentication cannot complete this request | Provide an Xquik API key or connect X | | `402` | The account lacks credits | Stop and request account action | | `424` | The upstream X dependency failed | Retry with bounded backoff | | `429` | The Twitter API rate limit applies | Wait for reset guidance, then resume the cursor | | `502` | The X dependency returned an invalid response | Retry later without changing IDs | Do not restart pagination after `429`. Retain `next_cursor` and completed result identifiers. Deduplicate recovered results through the exact `tweet_id`. POST and DELETE routes document different statuses. Read each route before building retry logic. See [error handling](/guides/error-handling). ## Handoff Checklist Store `tweet_id`, `text`, `author_username`, `created`, `url`, `has_more`, `next_cursor`, and the original `q`. Store source `id` as `user_id`, plus `username`, `name`, `followers`, `verified`, `profile_picture`, `has_more`, `next_cursor`, and the source lookup or search query. Store each trend `name`, `rank`, `query`, and `description`. Keep response `count`, `woeid`, and the requested region with the run checkpoint. Store the returned monitor `id` as `monitor_id`, `event_types`, `next_billing_at`, the returned webhook `id` as `webhook_id`, `url`, and the one-time `secret` in a secret manager. On production deliveries, store `delivery_id` for receiver retry de-dupe and `stream_event_id` when one monitor event should process once across endpoint changes. Store `event_id`, `type`, `monitor_id`, `monitor_type`, `occurred_at`, `has_more`, `next_cursor`, and the `cursor` query for the next page. Store `extraction_id`, `status`, `poll`, and `export_after_complete`; poll before loading CSV, JSON, or XLSX rows. Store `tweet_id` or `write_action_id`, `reply_to_tweet_id`, `status`, `charged_credits`, and `poll`; do not resend pending writes. For tweets or replies, pass public URLs in `media` and store `tweet_id` or `write_action_id`. For DMs, upload first, pass one `media_id` in `media_ids`, store `message_id`, and leave `reply_to_message_id` unset. ## Choose Google ADK MCP or the REST API | Requirement | Google ADK with MCP | Direct REST API | | -------------------------------- | --------------------- | ----------------------------- | | Natural-language tweet research | Strong fit | Write query logic yourself | | Gemini multi-agent handoffs | Native fit | Add an orchestrator | | Strict scheduled follower export | Extra model step | Strong fit | | Human-approved tweet posting | ADK confirmation flow | Build approval state yourself | | Predictable latency and cost | Less predictable | More predictable | | Typed final handoff | `output_schema` | SDK or Pydantic model | Use MCP when Gemini chooses among related Twitter API operations. Use REST for fixed routes, scheduled exports, or latency-sensitive services. ## Migrate Google ADK 1.x MCP Code Google ADK 2.x keeps compatibility aliases. New code should use current API symbols. | Older pattern | Current pattern | | ---------------------------------------- | -------------------------------- | | `from google.adk.agents import LlmAgent` | `from google.adk import Agent` | | `MCPToolset` | `McpToolset` | | `StreamableHTTPServerParams` | `StreamableHTTPConnectionParams` | | Manual toolset cleanup | `async with InMemoryRunner(...)` | | Prompt-only JSON | Pydantic `output_schema` | `MCPToolset` now emits a deprecation warning. The current class uses the `McpToolset` capitalization. ## Verified Google ADK Package Versions These versions were checked on August 2, 2026. | Package | Checked compatible version | Compatible range used here | | ------------ | -------------------------- | ----------------------------------- | | `google-adk` | 2.6.1 | `>=2.6,<2.7` | | `mcp` | 1.29.0 | `>=1.24,<2` through the `mcp` extra | | `pydantic` | 2.13.4 | `>=2.12,<3` | Pin a tested minor range. Review ADK 2.x release notes before widening it. ## Google ADK Twitter API Questions ### Does Google ADK Support MCP? Yes. Python ADK connects to remote Streamable HTTP servers through `McpToolset`. Xquik publishes `explore` and `xquik` at `https://xquik.com/mcp`. ### How Does Google Agent Development Kit Differ From MCP? ADK orchestrates Gemini agents. MCP standardizes external tool discovery and invocation. Xquik serves the tools, while `McpToolset` connects the agent. ### Does Google ADK MCP Require Google Cloud or Cloud Run? No. The agent code can run wherever Python 3.10 runs. Google Cloud and Cloud Run are optional deployment targets. Xquik requires no local MCP server. ### How Do I Connect Google ADK to a Twitter API? Create `StreamableHTTPConnectionParams` with the Xquik MCP URL. Send the Xquik API key in the `x-api-key` header. Pass `McpToolset` to `Agent.tools`. ### How Do I Search Tweets With a Gemini Agent? Ask the agent to call `GET /api/v1/x/tweets/search`. Supply an exact `q`, `queryType`, and limit. Preserve `tweet_id`, `created`, and `next_cursor`. This Twitter API integration can get tweets without X Developer credentials. Private routes still require a connected X account. ### Can Google ADK Post Tweets and Replies? Yes. A connected X account is required. Route every `xquik` execution through ADK confirmation. Validate the selected account, text, reply ID, and media. See [create tweet](/api-reference/x-write/create-tweet) for the exact contract. ### How Do I Handle Twitter API Rate Limits in Python? Treat `429` separately from dependency errors. Save the current cursor and completed tweet IDs. Resume after the reset guidance. Never retry in a tight loop. ### Can a Google ADK Agent Export Twitter Followers? Yes. The [followers API](/api-reference/x/followers) paginates follower profiles through response cursors. Extraction jobs produce larger CSV, JSON, or XLSX exports. Load exported rows only after polling confirms completion. ### Can Google ADK Monitor Tweets Without Repeated Searches? Yes. Create an account or keyword monitor. Replay stored events by cursor. Connect a webhook when the receiver can verify signatures and deduplicate deliveries. Monitors use scheduled checks and do not promise real time delivery. ### Google ADK or LangChain for a Twitter Agent? Choose ADK for Gemini-centered sessions and native agent handoffs. LangGraph emphasizes a broader model ecosystem and graph orchestration. Xquik supports both frameworks. ### Why Does a Remote MCP Connection Close Between Calls? Keep one runner active during related turns. Its context manager closes each `McpToolset` instance. Increase connection and SSE timeouts for long operations. Do not create one `McpToolset` per prompt. ### Does Tool Filtering Make the Twitter API Read-Only? Only `explore` without `xquik` blocks execution. The `xquik` tool can run every operation authorized by its key. Separate agents and approvals protect X actions. # Accountless Twitter Scraper API with Guest Wallets Source: https://docs.xquik.com/guides/guest-wallets Read tweets, profiles, followers, replies, timelines, and communities without an Xquik account. No connected X account is required. Fund one guest key.
For the complete documentation index, see llms.txt.
Guest wallets provide prepaid access to 33 eligible X read routes without an account. Here, account means an Xquik account. No connected X account is required. Use one funded guest key to search tweets. Read profiles, followers, replies, timelines, communities, or lists. No email address or dashboard is required. This is accountless access, not anonymous access. Every guest-wallet paid read requires the active guest key returned during wallet creation. Direct MPP reads use a per-request payment credential. A `401` or `402` response never creates a checkout. Show the payment choices and amount first. Create a hosted checkout only after the user explicitly confirms. Never submit payment for the user. ## Accountless Twitter Scraper API Questions ### What Twitter APIs Work Without Connecting an X Account? Xquik guest wallets cover 33 documented GET routes. They read tweets, replies, threads, profiles, followers, following, and timelines. Other routes cover communities, lists, trends, relationships, and articles. They do not require a connected X account. The caller still needs one active guest key. Create the wallet, let the user complete checkout, then poll its status. Start reads only when the response reports `usable: true`. Eligible routes cover tweet lookups, search, replies, threads, and profiles. They also cover followers, following, timelines, communities, lists, and trends. Relationship and article routes are also eligible. Guest access excludes posts, likes, reposts, follows, and messages. It also excludes monitors, webhooks, extractions, draws, and account management. Check the 33-route list below before building the request. ### Can I Scrape Twitter Without an API Account? You can read eligible X content without an Xquik account. No email address, dashboard login, or OAuth connection is required. This accountless Twitter scraper uses a prepaid guest key for authentication and credit tracking. Accountless does not mean anonymous. Keep the guest key secret. Send it as a Bearer credential on each eligible read. A missing or inactive key returns the documented `401` or `402` response. Create a wallet only after the user confirms the amount. The hosted checkout does not prove activation. Poll the status route and require `usable: true`. Store the guest key in a secret manager. Never place it in a URL, log, prompt, or shared export. Use full account credentials for writes, monitors, and webhooks. They also cover extraction jobs, draws, and management routes. ### Twitter API No Account Required It means no Xquik account and no connected X account are required. It does not remove authentication, payment confirmation, route scope, or rate limits. The guest key proves access to its funded wallet. Guest access excludes tweet posting, replies, likes, reposts, and follows. It also excludes messages, monitors, webhooks, extractions, draws, and management. Use a full account key or OAuth token for those operations. The guest wallet remains prepaid. The user chooses and confirms a supported amount. They complete hosted checkout and await verified activation. Each eligible read consumes credits under its documented route contract. A `401` or `402` response only presents recovery choices. It does not authorize wallet creation or payment. ### Accountless Twitter Scraper Use an accountless Twitter scraper for read-only searches and public profiles. It does not require an Xquik account. A funded guest key can call 33 listed GET routes. It can retrieve tweets, replies, threads, profiles, followers, and following. It can also read timelines, communities, lists, trends, relationships, and articles. This path still uses authentication, credits, route limits, and rate limits. It cannot post, like, repost, follow, or send messages. It cannot create monitors, webhooks, extraction jobs, draws, or account changes. Use stable Tweet and user IDs in stored results. Keep the guest key secret. Poll wallet status before the first paid read. ### Guest Key Twitter API Use a guest key for prepaid read-only workflows. It fits tweet search, profile lookup, follower pages, and reply pages. It also fits timelines, community reads, and list reads. Use direct MPP when one supported request carries its own payment. Use a full account credential when the workflow needs broader API coverage. Choose the access method before coding retries. Each method returns different payment and authentication recovery actions. Create the wallet after the user confirms the amount. Send a unique UUID v4 idempotency key. Store both secrets securely. Give only the hosted checkout URL to the user. Poll status until `usable: true`. Reuse the same guest key after an approved top-up. Do not create a new wallet when one paid read returns `insufficient_credits`. ## Choose an access method | Access method | Account | Credential | Coverage | Payment | | --------------- | ------------ | ------------------------------ | ------------------------------------------ | ---------------------------------------------------- | | Account API key | Required | Full-scope API key | Full authenticated API | Available account credits; plans add monthly credits | | OAuth 2.1 | Required | OAuth Bearer token | Same account capabilities granted by OAuth | Available account credits; plans add monthly credits | | Guest wallet | Not required | `paid_reads` API key | Exactly 33 prepaid GET routes | $10-$250 USD hosted checkout | | MPP | Not required | Per-request payment credential | 7 fixed-price GET operations | Per-request payment | Guest wallets do not grant write actions, connected-account reads, monitors, webhooks, extractions, draws, account management, billing management, API-key management, or OAuth access. Full account keys and OAuth behavior remain unchanged. ## Create and activate a wallet Ask the user to choose and explicitly confirm a USD amount from $10 through $250. Represent it in cents as `amount_minor`. Call `POST /api/v1/guest-wallets` with `Content-Type: application/json`, a cryptographically random UUID v4 `Idempotency-Key`, and the confirmed amount. ```bash theme={null} idempotency_key=$(uuidgen | tr '[:upper:]' '[:lower:]') response=$(curl -sS -X POST https://xquik.com/api/v1/guest-wallets \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $idempotency_key" \ -d '{"amount_minor": 1000, "currency": "usd"}') api_key=$(jq -r '.api_key' <<<"$response") checkout_url=$(jq -r '.checkout_url' <<<"$response") # Store $api_key and $idempotency_key as secrets. Give only $checkout_url to the user. ``` This request creates a one-use hosted checkout. It does not charge the user. Store the returned `api_key` and the original `Idempotency-Key` in a secret manager. The key appears only in the initial response and an exact idempotent replay. No email recovery is available. Give only `checkout_url` to the user. The user completes hosted checkout. Pending checkouts expire after 60 minutes. After payment, poll `GET /api/v1/guest-wallets/status` every `poll_after_seconds` with the guest key. Stop when `latest_purchase.status` is no longer `pending`. ```bash theme={null} curl https://xquik.com/api/v1/guest-wallets/status \ -H "Authorization: Bearer xq_your_guest_key_here" | jq ``` The key remains inactive for paid reads until status reports `usable: true`. Checkout completion alone is not proof of activation. Xquik activates credits only after payment is verified. ## Call paid read routes Send an active guest key as a Bearer credential: ```bash theme={null} curl "https://xquik.com/api/v1/x/tweets?ids=1893456789012345678,1893456789012345679" \ -H "Authorization: Bearer xq_your_guest_key_here" | jq ``` The `paid_reads` scope permits exactly the 33 GET routes listed below. It includes batch `GET /api/v1/x/tweets`, which accepts up to 100 tweet IDs. Every other route is unavailable. ## Eligible paid-read routes Guest wallets cover these 33 prepaid GET routes: * **Tweets:** `/api/v1/x/tweets`, `/api/v1/x/tweets/{id}`, `/api/v1/x/tweets/search`, `/api/v1/x/tweets/{id}/favoriters`, `/api/v1/x/tweets/{id}/quotes`, `/api/v1/x/tweets/{id}/replies`, `/api/v1/x/tweets/{id}/retweeters`, `/api/v1/x/tweets/{id}/thread` * **Users:** `/api/v1/x/users/batch`, `/api/v1/x/users/search`, `/api/v1/x/users/{id}`, `/api/v1/x/users/{id}/followers`, `/api/v1/x/users/{id}/followers-you-know`, `/api/v1/x/users/{id}/following`, `/api/v1/x/users/{id}/likes`, `/api/v1/x/users/{id}/media`, `/api/v1/x/users/{id}/mentions`, `/api/v1/x/users/{id}/replies`, `/api/v1/x/users/{id}/tweets`, `/api/v1/x/users/{id}/verified-followers` * **Communities:** `/api/v1/x/communities/{id}/info`, `/api/v1/x/communities/{id}/members`, `/api/v1/x/communities/{id}/moderators`, `/api/v1/x/communities/{id}/tweets`, `/api/v1/x/communities/search`, `/api/v1/x/communities/tweets` * **Lists:** `/api/v1/x/lists/{id}/followers`, `/api/v1/x/lists/{id}/members`, `/api/v1/x/lists/{id}/tweets` * **Trends:** `/api/v1/trends`, `/api/v1/x/trends` * **Relationships:** `/api/v1/x/followers/check` * **Articles:** `/api/v1/x/articles/{tweetId}` Seven routes also accept [direct MPP payment](/mpp/machine-payments-protocol#eligible-endpoints). The other 26 routes require a guest or full account credential. ## Top up an active wallet When a guest read returns `402 insufficient_credits`, its `payment_options` advertises only `POST /api/v1/guest-wallets/topups`. Ask the user to choose and confirm $10-$250 USD. Then create a new one-use hosted checkout with the existing guest key and a new UUID v4 `Idempotency-Key`. The top-up response keeps the same wallet and key. It never returns a new key. After payment, poll the same status URL every `poll_after_seconds` until `latest_purchase.status` is no longer `pending`. See [Top up guest wallet](/api-reference/guest-wallets/topup) for the request and response contract. ## Handle anonymous 401 and 402 responses The 26 non-MPP paid reads return `401` with a Bearer authentication challenge and a guest wallet creation action: ```text theme={null} HTTP/2 401 WWW-Authenticate: Bearer realm="xquik" Content-Type: application/json ``` The JSON body includes `payment_options.guest_wallet.create_checkout`. That action describes the guest wallet route, amount bounds, required UUID v4 header, and returned fields. The Bearer header requests authentication. It is not a Payment challenge. The 7 [direct MPP operations](/mpp/machine-payments-protocol#eligible-endpoints) return `402 application/problem+json` with `WWW-Authenticate: Payment` and the same guest wallet action. Complete the MPP challenge or ask the user to confirm a guest wallet amount. Do not create a guest wallet because a request returned `401` or `402`. The guest wallet action is an offer, not authorization. ## Use guest keys with MCP An active guest key can authenticate the API MCP server. Its `explore` and `xquik` tools expose only the 33 eligible GET read routes. Mutations and noneligible routes are unavailable. The 3 guest credential routes remain direct REST only: * `POST /api/v1/guest-wallets` * `POST /api/v1/guest-wallets/topups` * `GET /api/v1/guest-wallets/status` MCP cannot execute these routes. It may explain the direct REST flow, but the caller must wait for user confirmation before using it. See [MCP tools](/mcp/tools) for the scope-specific catalog. ## Protect wallet access * Keep `api_key` and `Idempotency-Key` out of URLs, logs, prompts, and shared output. * Respect `Cache-Control: no-store, private` on create, top-up, and status responses. * Reuse an idempotency key only for the exact same request. * Use `usable` and the returned status instead of inferring access from checkout state. * Refunds and disputes reconcile only affected-purchase credits. Unrelated credits remain usable. * Access pauses only during unresolved settlement risk or unrecovered liability. It resumes after resolution. ## Next steps * [Create guest wallet](/api-reference/guest-wallets/create) * [Get guest wallet status](/api-reference/guest-wallets/status) * [Top up guest wallet](/api-reference/guest-wallets/topup) * [MPP overview](/mpp/machine-payments-protocol) * [Authentication](/api-reference/authentication) # Haystack Twitter Search API & RAG Python Guide Source: https://docs.xquik.com/guides/haystack Build Haystack RAG pipelines with Twitter search and user timeline APIs, typed Documents, citations, pagination, async Python, and exact error handling.
For the complete documentation index, see llms.txt.
Haystack is an open source framework for Python AI applications. Compare frameworks for building RAG and agent pipelines. Use [`xquik-haystack`](https://github.com/Xquik-dev/xquik-haystack) for current tweets. This Haystack AI framework gives you typed tweet `Document` objects. Each includes text, authors, metrics, and URLs. This Haystack AI API integration supports RAG pipelines and agent workflows. You can follow this guide when building AI applications with Haystack and current tweets. The integration never grants write access. The integration provides two read-only components: `XquikTweetSearch` searches keywords, hashtags, accounts, conversations, and exact phrases. `XquikUserTweetsFetcher` retrieves one public account's tweets and optional replies. Use these components for search, timelines, monitoring, and retrieval-augmented generation. They can retrieve relevant tweets for Haystack AI agents and Haystack AI RAG pipelines. These components are building blocks for pipelines and agents. Wrap `XquikTweetSearch` with `ComponentTool` when an agent needs a search tool called `search_current_tweets`. Use the [followers API](/api-reference/x/followers) for follower exports. Use the [write API](/api-reference/x-write/create-tweet) for approved publishing. Those actions stay outside this integration. ## Install Haystack and Xquik Use Python 3.10 or newer. Pin both packages for repeatable pipeline builds. ```bash theme={null} python -m pip install "xquik-haystack==0.1.3" "haystack-ai==3.0.0" ``` Install inside a virtual environment. Release `0.1.3` is published on PyPI. Haystack `3.0.0` supports sync and async runs. The `pip install` command pins both packages for repeatable builds. Create an [Xquik API key](/x-api-quickstart), then export it locally. ```bash theme={null} export XQUIK_API_KEY="xq_..." ``` Never embed production keys in pipeline YAML, notebooks, or source control. Load the environment variable through a Haystack `Secret` object. ## Search Twitter Tweets in Python `XquikTweetSearch` calls the `GET /x/tweets/search` search endpoint. It accepts standard X search syntax and structured filters. Use it for each Twitter API keyword search or exact phrase query. ```python theme={null} from haystack import Pipeline from haystack.utils import Secret from haystack_integrations.components.websearch.xquik import XquikTweetSearch search = XquikTweetSearch( api_key=Secret.from_env_var("XQUIK_API_KEY"), top_k=20, query_type="Latest", ) pipeline = Pipeline() pipeline.add_component("twitter_search", search) result = pipeline.run( { "twitter_search": { "query": '"retrieval augmented generation" lang:en -filter:retweets' } } ) documents = result["twitter_search"]["documents"] links = result["twitter_search"]["links"] ``` Use `Latest` for recent monitoring. Use `Top` for engagement-ranked discovery. Store tweet IDs because rankings can change. Save every search query beside its tweet IDs. Set `top_k` to cap the number of tweets in each run. ### Build Focused Tweet Searches | Search intent | Query example | | --------------- | -------------------------------------------- | | Exact phrase | `"retrieval augmented generation"` | | Account posts | `from:deepset_ai haystack` | | Hashtag search | `#haystack #rag` | | Date window | `haystack since:2026-07-01 until:2026-08-01` | | Exclude reposts | `haystack -filter:retweets` | The API also supports structured search filters. Pass them through `extra_params` during initialization. ```python theme={null} search = XquikTweetSearch( top_k=50, query_type="Latest", extra_params={ "language": "en", "mediaType": "links", "minFaves": 10, "replies": "exclude", }, ) ``` See the [tweet search API](/api-reference/x/search-tweets) for author, reply, quote, URL, and conversation filters. Keep timestamps and cursors outside `extra_params`. ## Choose a Search Window Bound every retrieval with timestamps. ```python theme={null} result = search.run( query="haystack agents", since_time="2026-07-31T00:00:00Z", until_time="2026-08-01T00:00:00Z", ) ``` Save each window. Deduplicate overlaps by `Document.meta["id"]`. ## Fetch a Twitter User Timeline Use `XquikUserTweetsFetcher` for `GET /x/users/{id}/tweets`. Pass a username or numeric X user ID. ```python theme={null} from haystack.utils import Secret from haystack_integrations.components.websearch.xquik import ( XquikUserTweetsFetcher, ) timeline = XquikUserTweetsFetcher( api_key=Secret.from_env_var("XQUIK_API_KEY"), include_replies=False, include_parent_tweet=False, ) result = timeline.run(user_id="deepset_ai") documents = result["documents"] ``` Set `include_replies=True` for replies. Enable parent tweets only when reply context matters. Choose search for many accounts and timelines for one account. ## Understand Haystack Document Fields Each tweet becomes one Haystack `Document`. Tweet text becomes `Document.content`. Stable fields become metadata. | Document field | Stored tweet value | | ------------------------------------------------------------ | -------------------------------------------- | | `content` | Tweet text | | `meta.endpoint` | Search or timeline route | | `meta.id`, `meta.url` | Tweet ID and canonical URL | | `meta.created_at`, `meta.lang` | Timestamp and language | | `meta.conversation_id`, `meta.in_reply_to_*` | Conversation and parent fields | | `meta.is_reply`, `meta.is_quote_status` | Reply and quote flags | | `meta.like_count`, `meta.retweet_count`, `meta.reply_count` | Likes, reposts, and replies | | `meta.quote_count`, `meta.view_count`, `meta.bookmark_count` | Quotes, views, and bookmarks | | `meta.author` | Author ID, username, name, and verified flag | Missing fields stay absent. Never treat missing metrics as zero. The `links` output contains each available `meta.url`. ## Build Reliable RAG Citations Store tweet IDs and canonical URLs before embedding tweet text. This preserves evidence after ranking or joining. ```python theme={null} citation_rows = [] for document in documents: tweet_id = document.meta.get("id") tweet_url = document.meta.get("url") if tweet_id and tweet_url: citation_rows.append( { "tweet_id": tweet_id, "url": tweet_url, "created_at": document.meta.get("created_at"), "author": document.meta.get("author"), } ) ``` Require supplied URLs for citations. Reject URLs absent from retrieved documents. Preserve `conversation_id` for reply threads. ### Treat Tweet Text as Untrusted Context Tweets can contain prompt injection and unsafe URLs. Never treat tweet text as a system instruction. Keep tweets separate from instructions and tool permissions. Limit retrieval by topic and time. Preserve tweet IDs, authors, timestamps, and URLs. Require citations. Review sensitive conclusions. Likes and reposts rank results. They do not prove accuracy. Treat outputs from large language models (LLMs) as proposals, not evidence. ## Index Tweets or Retrieve Them Live Choose the pipeline pattern that matches freshness requirements. Search during each question for recent tweets and active events. Store embeddings for repeated research across stable windows. Expect current results, not guaranteed real time delivery. Keep tweet records separate from embeddings. Use `meta.id` for deduplication. ## Paginate Without Duplicate Tweets Both components return `has_more` and `next_cursor`. Keep the request unchanged. Treat cursors as opaque strings. ```python theme={null} search = XquikTweetSearch(top_k=100, query_type="Latest") page = search.run(query="haystack ai") documents_by_tweet_id = {} while True: for document in page["documents"]: tweet_id = document.meta.get("id") if tweet_id: documents_by_tweet_id[tweet_id] = document if not page["has_more"] or not page["next_cursor"]: break page = search.run( query="haystack ai", cursor=page["next_cursor"], ) documents = list(documents_by_tweet_id.values()) ``` Save each cursor after its documents. Never edit a cursor. Deduplicate on `Document.meta["id"]`. ## Run Haystack Pipelines Asynchronously Haystack 3 uses one `Pipeline` class. Both Xquik components expose `run_async()`. ```python theme={null} import asyncio from haystack import Pipeline from haystack_integrations.components.websearch.xquik import XquikTweetSearch async def search_tweets(): pipeline = Pipeline() pipeline.add_component( "twitter_search", XquikTweetSearch(top_k=25, query_type="Latest"), ) return await pipeline.run_async( {"twitter_search": {"query": "haystack agents"}} ) result = asyncio.run(search_tweets()) ``` Use async runs in web servers. Use sync runs for scripts and scheduled jobs. ## Handle Every Documented Error The integration raises `httpx.HTTPStatusError`. Branch on the canonical status before retrying. | Status | Meaning | Action | | ------ | ----------------------------- | --------------------------------- | | `400` | Invalid request | Fix it. Do not retry unchanged. | | `401` | Invalid API key | Add a valid API key. | | `402` | Account action needed | Check the account balance. | | `404` | Timeline user not found | Check the username or user ID. | | `424` | X retrieval dependency failed | Retry with bounded backoff. | | `429` | Rate limit exceeded | Back off before retrying. | | `502` | X retrieval error | Retry up to the configured limit. | ```python theme={null} import httpx from haystack_integrations.components.websearch.xquik import XquikTweetSearch search = XquikTweetSearch(top_k=20, max_retries=3) try: result = search.run(query="haystack ai") except httpx.HTTPStatusError as error: status = error.response.status_code if status in {400, 401, 402, 404}: raise RuntimeError(f"Fix Xquik request before retrying: HTTP {status}") from error if status in {424, 429, 502}: raise RuntimeError(f"Retry Xquik request with backoff: HTTP {status}") from error raise ``` Record status codes, never credentials. Cap retries to prevent unbounded agent loops. ## Pipeline Handoff Use this shape when Haystack hands results to a vector store, evaluation job, queue, CSV export, or dashboard. Store content, tweet IDs, URLs, timestamps, authors, and public metrics. Join each canonical URL to `meta.id`. Store the request, options, `has_more`, and `next_cursor`. Store each HTTP status with its pipeline run ID. Keep handoff records separate from embeddings. Later runs can refresh tweets without rebuilding the pipeline. ## Haystack Component or Direct REST API Does your pipeline already return `Document` objects? Add these components directly. They normalize tweet text, metadata, URLs, and pagination. Use direct Xquik REST routes for followers, following, replies, quotes, reposts, lists, communities, media, trends, monitors, approved writes, and extra query options. Both approaches use the same Xquik contracts. Components only support tweet search and user timelines. ## Migrate to Haystack 3 Haystack 3 replaces `AsyncPipeline` with `Pipeline`. Call `await pipeline.run_async(...)` for concurrent execution. Keep `meta.id` as the tweet identity. Test with `haystack-ai==3.0.0` before upgrading. Check the [official migration guide](https://docs.haystack.deepset.ai/docs/migration) for other changes. ## Common Haystack Twitter API Questions ### What Is Haystack AI? Haystack links search, RAG, and AI agents inside Python pipelines. ### How Does the Twitter API Search Tweets? It accepts queries, filters, ordering, and cursors. Xquik returns tweets and citation URLs. ### What Is a Twitter Search API Python Workflow? Install both packages. Run `XquikTweetSearch`, then process documents, links, and cursors. ### How Does an API Twitter Search Workflow Preserve Cursors? Save `next_cursor` after each page. Reuse every filter. ### How Does the Twitter API Search Tweets by Keyword? Pass keywords, phrases, hashtags, accounts, or filters. Choose `Latest` or `Top`. ### Haystack AI vs LangChain: Which Fits Twitter RAG? Choose Haystack for `Document` pipelines. Choose LangChain for its retrievers and tools. ### Where Is the Haystack AI GitHub Integration? The [Xquik Haystack repository](https://github.com/Xquik-dev/xquik-haystack) contains the package and offline tests. ### Can a Haystack AI Agent Publish Tweets? No. This integration reads searches and timelines. Use the write API for approved tweets. ### Does This Require a Haystack Enterprise Platform? No. Run the open-source components in your Haystack setup. ## Source and Contracts * [Xquik Haystack repository](https://github.com/Xquik-dev/xquik-haystack) * [Xquik Haystack PyPI package](https://pypi.org/project/xquik-haystack/) * [Haystack 3.0 release](https://github.com/deepset-ai/haystack/releases/tag/v3.0.0) * [Search Tweets API](/api-reference/x/search-tweets) * [Get User Timeline API](/api-reference/x/user-tweets) # Hermes Tweet for Hermes Agent Twitter Tools Source: https://docs.xquik.com/guides/hermes-tweet Install Hermes Tweet for Hermes Agent. Search tweets, export followers and replies, monitor X accounts, and run approved Twitter actions through Xquik.
For the complete documentation index, see llms.txt.
Hermes Tweet is Xquik's native Hermes Agent Twitter plugin. It provides a structured X automation toolset. The plugin adds catalog-guided Twitter tools to the Hermes Agent runtime. The Python package includes read tools, optional action tools, and 2 slash commands. It also bundles a reusable Hermes Twitter skill. That reusable skill explains supported tweet and profile workflows. Use Hermes Tweet for X search, profiles, followers, replies, monitors, and approved actions. Every live call starts from the bundled OpenAPI catalog. This design helps Hermes choose documented methods, paths, parameters, and response shapes. Each AI agent starts with catalog discovery. It then executes only a matching tool call. ## Why Add Twitter Tools to Hermes Agent Hermes Agent handles planning, memory, tools, and long-running workflows. Hermes Tweet adds focused X and Twitter API access through Xquik. The plugin exposes 102 agent-callable endpoints. Those endpoints cover tweets, profiles, timelines, followers, following, replies, media, monitors, and webhooks. Hermes Tweet separates discovery, reads, and approved actions. This separation reduces guessed routes and unintended writes. Use the plugin for these Hermes Agent Twitter tasks: * Search tweets by keyword, phrase, hashtag, or account. * Read tweet details, replies, quotes, and engagement counts. * Inspect profiles, timelines, followers, following, and mentions. * Export followers, following accounts, or tweet replies. * Monitor accounts or keywords and deliver webhook events. * Draft, post, reply, like, repost, follow, or send DMs. The read tool preserves complete JSON payloads. It does not collapse rich tweet, profile, media, or pagination fields. ## Prerequisites * Python `3.11` or newer * Hermes Agent with plugin support * [Xquik API key](/x-api-quickstart) Hermes exposes pip plugins through the `hermes_agent.plugins` entry-point group. Enable each third-party plugin before loading it. ## Install For an existing Hermes Agent install, use the plugin installer: ```bash theme={null} hermes plugins install Xquik-dev/hermes-tweet --enable ``` Hermes records plugin enablement in `~/.hermes/config.yaml`. The Hermes home directory is `~/.hermes`. Hermes prompts for `XQUIK_API_KEY` during an interactive install and stores it in `~/.hermes/.env`. Non-interactive installs skip the prompt. Set the key in the process environment or `~/.hermes/.env` before calling `tweet_read`. Install the published PyPI package directly into the Hermes Python environment when you manage package installation yourself: ```bash theme={null} uv pip install --python ~/.hermes/hermes-agent/venv/bin/python hermes-tweet hermes plugins enable hermes-tweet ``` If your Hermes Python environment includes `pip`, this path is also valid: ```bash theme={null} ~/.hermes/hermes-agent/venv/bin/python -m pip install hermes-tweet hermes plugins enable hermes-tweet ``` The current package version is `0.1.12`. The plugin name is `hermes-tweet`, and the Python entry point is `hermes-tweet = hermes_tweet`. ## Configure Set your Xquik API key before starting Hermes: ```bash theme={null} export XQUIK_API_KEY="xq_..." ``` For persistent Hermes sessions, add the key to `~/.hermes/.env`: ```bash theme={null} XQUIK_API_KEY=xq_... ``` Optional environment variables: ```bash theme={null} export XQUIK_BASE_URL="https://xquik.com" export HERMES_TWEET_ENABLE_ACTIONS="false" ``` Keep `HERMES_TWEET_ENABLE_ACTIONS=false` for unattended sessions. Enable actions only for workflows with an explicit approval step: ```bash theme={null} export HERMES_TWEET_ENABLE_ACTIONS="true" ``` If Hermes is already running after you edit `~/.hermes/.env`, run `/reload` in an interactive CLI session, or restart gateway and cron sessions before calling `tweet_read`. New agent runs read the updated environment after restart. ## Tools Search the bundled Xquik endpoint catalog without making an API call. Use it before every live endpoint call. Call catalog-listed read-only endpoints after `XQUIK_API_KEY` is configured. Call write-like or private endpoints only when `HERMES_TWEET_ENABLE_ACTIONS=true`. Start with `tweet_explore`. Discover the method, path, parameters, and response shape before any endpoint call. ```text theme={null} Use tweet_explore to find the endpoint for user lookup, then call tweet_read for @username. ``` ## Hermes Agent Twitter Search Start a tweet search with a concrete question. Ask `tweet_explore` for the catalog route before making a live call. For keyword searches, use `GET /api/v1/x/tweets/search`. Pass a specific `q` value and a bounded `limit`. Useful queries combine a topic with intent. Examples include product feedback, support complaints, launches, hiring, or competitor mentions. Return fields needed by the next workflow step. Typical fields include tweet IDs, text, authors, timestamps, metrics, media, and entities. Preserve `next_cursor` whenever `has_next_page` is true. Continue only when the workflow needs another page. Do not treat every matching tweet as equally relevant. Filter results by the task's topic, author, language, date, or engagement criteria. For a tweet search agent, summarize patterns after collecting the selected pages. Keep source tweet IDs beside each conclusion. ## Profiles, Followers, Following, and Replies Hermes Tweet can inspect an account before starting a larger export. Use catalog-listed reads for profiles, timelines, followers, following, and mentions. For small lists, use the matching read endpoint. Follow the returned cursor until the requested result count is complete. For bulk work, use the extraction workflow. Available extraction types include `follower_explorer`, `following_explorer`, and `reply_extractor`. Estimate each extraction before creating it. Confirm the target username, tweet ID, result limit, format, and expected scope. After approval, create the extraction with `tweet_action`. Poll its status with `tweet_read` until the job becomes terminal. Export completed results as CSV, JSON, or XLSX. Retain stable tweet or user IDs for later joins. A Twitter follower export supports audience analysis and CRM enrichment. A following export reveals accounts that the target follows. A reply export supports conversation analysis, support triage, and giveaway review. Never infer sentiment from usernames alone. ## Monitors and Webhook Delivery Use an account monitor for new posts from selected profiles. Use a keyword monitor for matching tweets across X. Create monitors only after confirming the target and cadence. Register a webhook only after reviewing its receiver URL. Store the webhook secret outside Hermes transcripts. Verify `X-Xquik-Signature` against the exact raw request body. Persist `deliveryId` and `streamEventId` for deduplication. Return `2xx` when a duplicate was already accepted. Monitor events can trigger summaries, alerts, CRM updates, or research queues. Keep each downstream action separately approved. ## Approved Twitter Actions Hermes Tweet keeps `tweet_action` unavailable by default. Enable it only for workflows that can change state. Before every action, review the method, path, payload, account, and expected effect. Require explicit approval for the exact request. Supported action areas include tweets, replies, likes, reposts, follows, DMs, profiles, media, communities, and lists. Use a unique `Idempotency-Key` for durable write actions. Store the returned action ID and status URL. Poll while `terminal` is false. Retry only when `safeToRetry` is true. For tweet media, pass public HTTPS image or MP4 URLs in `media`. For DM media, upload first and pass its `mediaId`. Keep full DM bodies out of shared outputs. Return only the fields needed for confirmation. ## Workflow Handoffs Use `tweet_explore` first, then choose `tweet_read` for public reads or `tweet_action` for approved jobs that create or change state. Use `tweet_read` with `GET /api/v1/x/tweets/search`, a concrete `q`, and a bounded `limit` to return tweet IDs, text, authors, timestamps, and metrics. Use `tweet_action` to estimate and create `follower_explorer`, then use `tweet_read` to poll the job and export CSV, JSON, or XLSX results. Use `tweet_explore` with `include_actions true` to find monitor and webhook endpoints, then use `tweet_action` for `POST /api/v1/monitors` or `POST /api/v1/monitors/keywords` and `POST /api/v1/webhooks` only after approval. Store the webhook `secret` in a secret manager. In receivers, verify `X-Xquik-Signature`, store `deliveryId` and `streamEventId`, return `2xx` for accepted duplicates, and keep endpoint signing values, raw request body, raw signature, and full headers out of Hermes transcripts and shared workflow outputs. Use public media URLs in `media` for tweet or reply actions. Store the durable action `id`, `status`, `billing`, `result`, and `statusUrl`. Poll with `tweet_read` while `terminal` is false. For DM attachments, upload media first, pass one returned `mediaId` in `media_ids`, then store the DM action. Keep full DM bodies out of shared outputs and leave `reply_to_message_id` unset. ```text theme={null} Use tweet_explore to find tweet search endpoints. Use tweet_read for GET /api/v1/x/tweets/search with q "AI agents". Set limit 25. Return tweet id, text, author username, createdAt, and engagement counts. ``` ```text theme={null} Use tweet_explore with include_actions true to find follower export endpoints. Estimate follower_explorer for @username with resultsLimit 10000. Create the job with tweet_action only after approval. Poll /api/v1/extractions/{id}, then export CSV, JSON, and XLSX with tweet_read. ``` ```text theme={null} Use tweet_explore with include_actions true to find monitor and webhook endpoints. Create an account monitor or keyword monitor with tweet_action only after approval. Register the receiver URL with tweet_action for POST /api/v1/webhooks. Store the webhook secret in a secret manager. Verify X-Xquik-Signature, store deliveryId and streamEventId, and return 2xx for accepted duplicates. Keep endpoint signing values and raw request bodies out of Hermes transcripts. Also exclude raw signatures and full headers from shared workflow outputs. ``` ```text theme={null} Use tweet_explore with include_actions true to find media write endpoints. For a tweet or reply, call tweet_action for POST /api/v1/x/tweets. Set media to public HTTPS image or MP4 URLs. Do not send media_ids. For tweet_action, send a unique Idempotency-Key. Store id, status, billing, result, and statusUrl. Poll with tweet_read while terminal is false. Retry only when safeToRetry is true, using a new key. For a DM attachment, call tweet_action for POST /api/v1/x/media first. Then call POST /api/v1/x/dm/{userId} with one media_ids value. Leave reply_to_message_id unset. Return the complete action record. Read the confirmed resource ID from result.id. Keep full DM bodies out of shared outputs. ``` ## Runtime Diagnostics For scriptable checks, use `hermes tools list`. Bare `hermes tools` opens the interactive tool UI and requires a TTY. The list command reports plugin toolsets, so confirm `hermes-tweet` appears before testing calls. ```bash theme={null} hermes tools list ``` Run a non-mutating one-shot probe after the plugin is enabled: ```bash theme={null} hermes -z "Use tweet_explore, then read /api/v1/x/trends. Do not call tweet_action." --toolsets hermes-tweet ``` Expected behavior: * `tweet_explore` can inspect catalog endpoints without an API key. * Without `XQUIK_API_KEY`, a non-mutating Hermes probe exposes `tweet_explore` only. * After configuration and restart, `tweet_read` can read `/api/v1/x/trends`. * `tweet_action` stays hidden or disabled unless `HERMES_TWEET_ENABLE_ACTIONS=true`. * `/xstatus` and `/xtrends` appear in the Hermes plugin command registry. Hermes one-shot prompts do not dispatch `/xstatus` as an interactive slash command. Verify slash commands in an active CLI or gateway session. Use `hermes -z` for tool-call probes. Non-interactive installs cannot prompt for credentials; set `XQUIK_API_KEY` in the process environment or `~/.hermes/.env`. ## Slash Commands Show Xquik account, subscription, and usage status in an active Hermes CLI or gateway session. Show current X trends from the plugin command registry. ## Safety Model Hermes Tweet reads auth from environment variables and injects it at request time. The model does not receive the API key as a tool argument. The plugin blocks dashboard-only administrative endpoints from the catalog. It also blocks billing, credits, support, API-key, and reauthentication endpoints. Private reads and write-like endpoints use `tweet_action`. The tool stays hidden unless `HERMES_TWEET_ENABLE_ACTIONS=true`. For unattended jobs, keep action tools disabled and use only `tweet_explore` plus `tweet_read`. ## API Coverage Hermes Tweet includes 102 agent-callable Xquik endpoints. OpenAPI generates the catalog. The catalog includes 7 MPP-tagged read endpoints at fixed prices. Tweet search, tweet lookup, user lookup, timelines, articles, and trends. Account status, connected accounts, usage, and events. Account monitors, keyword monitors, events, and webhooks. Extractions, giveaway draws, exports, compose, drafts, and styles. Tweet, reply, like, retweet, follow, DM, profile, media, and communities. ## Local Development For local plugin development, regenerate the bundled catalog from the Xquik OpenAPI contract: ```bash theme={null} python scripts/build_catalog.py ../xquik/openapi.yaml ``` Run that command from the `hermes-tweet` repository after the Xquik OpenAPI file changes. ## Verify After installing, enabling, and setting `XQUIK_API_KEY`, run: ```text theme={null} /xstatus ``` For scriptable diagnostics, list the plugin toolset: ```bash theme={null} hermes tools list ``` Then test a read workflow: ```text theme={null} Use tweet_explore to find X trends, then use tweet_read to return current trends. ``` For action workflows, require an explicit draft and approval step before enabling `tweet_action`. ## Troubleshooting Run `hermes plugins enable hermes-tweet`, then confirm `hermes-tweet` appears in `hermes tools list`. Confirm `XQUIK_API_KEY` is exported in the same environment that starts Hermes, or stored in `~/.hermes/.env`. Run `/reload` in the interactive CLI, or restart gateway and cron sessions. Set `HERMES_TWEET_ENABLE_ACTIONS=true` only for approved action workflows. Regenerate the catalog from the current Xquik OpenAPI file in local development. Keep `HERMES_TWEET_ENABLE_ACTIONS=false` and use read tools only. ## Hermes Agent Twitter FAQ ### What Is Hermes Agent? Hermes Agent is an open-source agent runtime from Nous Research. It supports tools, skills, plugins, memory, scheduled work, and gateway sessions. Hermes Tweet extends that runtime with Xquik's Twitter API tools. It does not replace Hermes Agent itself. ### How Do I Install Hermes Tweet? Install the GitHub package with `hermes plugins install Xquik-dev/hermes-tweet --enable`. This command installs and enables the plugin. You can also install `hermes-tweet` from PyPI. Install it inside Hermes' Python environment, then enable the plugin. Restart Hermes after changing environment variables. Run `hermes tools list` before testing a live Twitter workflow. ### Is Hermes Tweet a Hermes Agent Skill or Plugin? Hermes Tweet is an executable Hermes Agent plugin. It registers `tweet_explore`, `tweet_read`, and `tweet_action`. The package also bundles a Hermes skill. That skill teaches endpoint discovery, read selection, approvals, and secret handling. A skill supplies operating guidance. The plugin supplies the Python tools that make API calls. ### How Does Hermes Agent Search Twitter? Hermes first calls `tweet_explore` with the search goal. The catalog returns a matching method, route, parameters, and response shape. Hermes then calls `tweet_read` for `GET /api/v1/x/tweets/search`. The `q` parameter contains the keyword, phrase, hashtag, or account query. The result can include tweets, authors, timestamps, engagement metrics, media, and pagination fields. The exact payload follows the Xquik API response. ### Can Hermes Agent Export Twitter Followers? Yes. Use `tweet_explore` to find `follower_explorer`, then estimate the requested result count. Create the extraction only after approval. Poll the extraction and export the completed followers as CSV, JSON, or XLSX. The same workflow supports `following_explorer`. Choose it when the task needs accounts followed by a profile. ### Can Hermes Agent Export Tweet Replies? Yes. Use `reply_extractor` for a bounded reply export. Confirm the target tweet ID and result limit before creation. Store tweet IDs, author IDs, timestamps, reply text, and available metrics. Preserve cursors or job status for reproducible collection. Use the exported replies for support triage, conversation research, or giveaway review. Apply project-specific privacy and retention rules. ### Can Hermes Agent Monitor X Accounts? Yes. Account monitors watch selected profiles. Keyword monitors watch matching tweets. Send monitor events to a verified webhook receiver. Deduplicate events before triggering alerts or downstream actions. ### Does Hermes Tweet Need X Developer Credentials? No X developer credentials are required. Hermes Tweet authenticates with an Xquik API key. Keep `XQUIK_API_KEY` in the Hermes runtime environment. Never pass that key through a model-visible tool argument. ### Hermes Agent vs OpenClaw: Which Plugin Should I Use? Choose Hermes Tweet for Hermes Agent. Choose TweetClaw for OpenClaw. Hermes Tweet is a Python entry-point plugin. TweetClaw is an npm plugin for OpenClaw. Both use the Xquik API contract. Both start with catalog discovery before live Twitter calls. Do not install both for one runtime. Pick the plugin that matches the agent host. ### Is Hermes Tweet an MCP Server? No. Hermes Tweet runs inside Hermes Agent and registers native Python tools. Use the [Xquik MCP server](/mcp/overview) when your client supports MCP. Use Hermes Tweet for native Hermes plugin loading. ### Why Are Hermes Tweet Tools Missing? Confirm installation with `hermes plugins`. Enable the plugin with `hermes plugins enable hermes-tweet`. Then run `hermes tools list`. Confirm the `hermes-tweet` toolset appears. Restart Hermes after configuration changes. Check the startup output for a plugin registration failure. An open Hermes Agent issue reports some import-time failures can remain cached. Restart after fixing the registration error. ### Where Is Hermes Tweet On GitHub? The official repository is [Xquik-dev/hermes-tweet](https://github.com/Xquik-dev/hermes-tweet). It contains source, tests, release notes, security policy, and plugin metadata. Use the repository issue tracker for reproducible plugin defects. Use these docs for supported Xquik workflows and API routes. ## References * [Hermes Tweet GitHub repo](https://github.com/Xquik-dev/hermes-tweet) * [Hermes Tweet on PyPI](https://pypi.org/project/hermes-tweet/) * [Hermes Agent plugin docs](https://hermes-agent.nousresearch.com/docs/user-guide/features/plugins) * [TweetClaw for OpenClaw](/guides/tweetclaw) * [API Reference](/api-reference/overview) * [MCP Server](/mcp/overview) # LangChain Twitter API Agent with MCP & LangGraph Source: https://docs.xquik.com/guides/langchain Build LangChain and LangGraph Twitter API agents for tweet search, profiles, followers, monitors, exports, and reviewed X actions through MCP. See Python code.
For the complete documentation index, see llms.txt.
Build a LangChain Twitter API agent through Xquik's MCP server. Search tweets, inspect profiles, export followers, replay monitor events, and review X actions. This LangChain Twitter API integration helps when building agents for Twitter. It preserves tweet IDs, timestamps, cursors, and route errors as typed values. ## Why Use LangChain With a Twitter API? LangChain connects Xquik tools to models, retrievers, databases, and application services. LangGraph adds durable state, resumable jobs, and human approval. Choose a narrow route for each Twitter agent task. | Agent task | Xquik route | Preserve for the next step | | --------------------- | ----------------------------- | ------------------------------------------------- | | Search tweets | `GET /api/v1/x/tweets/search` | Query, tweet IDs, authors, `created`, and cursor | | Inspect a profile | `GET /api/v1/x/users/{id}` | User ID, username, biography, and follower count | | Export followers | `POST /api/v1/extractions` | Extraction ID, status, poll URL, and export state | | Watch keywords | `POST /api/v1/monitors` | Monitor ID, event types, and next billing time | | Replay monitor events | `GET /api/v1/events` | Event IDs, `has_more`, and `next_cursor` | | Post or reply | The matching X write route | Tweet ID, action ID, status, and charged credits | Use LangChain for short tool-calling conversations. Use LangGraph when work must resume after failures, approvals, or process restarts. Both use the same MCP tools and normalized response contract. ## LangChain Twitter API Prerequisites * Python 3.10 or later * An [Xquik API key](/x-api-quickstart) beginning with `xq_` * A LangChain-supported model with tool and structured-output support * A connected X account for private reads or write actions Public X reads do not require X Developer credentials. Authenticate with Xquik. Connect an X account only when the selected route requires it. This supplies a Twitter API for Python agents through one MCP connection. Authenticate this MCP server with an Xquik API key. Do not send OAuth 2.0 or an OAuth token. Keep credentials outside user context, prompts, and agent memory. ## Install the Python Packages Install compatible minor ranges. This avoids silent breaking changes. ```bash theme={null} python -m pip install --upgrade \ "langchain>=1.3,<1.4" \ "langchain-mcp-adapters>=0.3,<0.4" \ langchain-anthropic \ python-dotenv ``` Model Context Protocol (MCP) powers LangChain MCP support. LangChain MCP support comes from `langchain-mcp-adapters`. This open-source adapter loads tool schemas from configured MCP server connections. MCP clients turn model tool calls into authenticated HTTP requests. ### Separate MCP Clients, Servers, and Model Credentials LangChain runs the MCP client. Xquik runs the MCP server. The client loads tool definitions before the first API call. The server validates each route, HTTP method, query, body, and Xquik API key. Use an OpenAI API key only with a compatible model provider. Keep every model credential separate from the Xquik key. Never send either credential through prompts, user context, tool arguments, or saved handoffs. Store secrets outside source control. ```bash .env theme={null} XQUIK_API_KEY=xq_YOUR_KEY_HERE ANTHROPIC_API_KEY=sk-ant-... ``` Add `.env` and generated handoff files to `.gitignore`. ```text .gitignore theme={null} .env xquik-*-handoff.json ``` ## How to Use the Twitter API in Python With LangChain Use Pydantic for the final handoff. Do not rename a text response to `.json`. Validated output rejects missing tweet IDs and malformed cursors. ```python theme={null} import asyncio import os from pathlib import Path from typing import Literal from dotenv import load_dotenv from langchain.agents import create_agent from langchain_mcp_adapters.client import MultiServerMCPClient from pydantic import BaseModel class TweetRow(BaseModel): tweet_id: str text: str author_username: str | None = None created: int | None = None url: str | None = None class TweetSearchHandoff(BaseModel): query: str route_used: str tweets: list[TweetRow] has_more: bool next_cursor: str | None = None stop_reason: Literal[ "complete", "requested_limit", "cursor_stalled", "page_cap", ] async def main() -> None: load_dotenv() client = MultiServerMCPClient( { "xquik": { "transport": "http", "url": "https://xquik.com/mcp", "headers": {"x-api-key": os.environ["XQUIK_API_KEY"]}, }, } ) tools = await client.get_tools() agent = create_agent( model="anthropic:claude-sonnet-4-6", tools=tools, response_format=TweetSearchHandoff, system_prompt=( "Use Xquik for Twitter API requests. Preserve exact IDs and cursors. " "Use GET /api/v1/x/tweets/search. Never invent missing tweet fields." ), ) result = await agent.ainvoke( { "messages": [ { "role": "user", "content": ( "Search 25 recent tweets about LangChain MCP. " "Return the query, route, tweet rows, cursor state, " "and an explicit stop reason." ), } ] } ) handoff = result["structured_response"] Path("xquik-langchain-handoff.json").write_text( handoff.model_dump_json(indent=2), encoding="utf-8", ) asyncio.run(main()) ``` Wait for `result = await`, then read `structured_response`. `MultiServerMCPClient` loads the `explore` and `xquik` tools. The client is stateless by default. Save every cursor, job ID, and write status externally. The `xquik` tool executes a bounded sandbox function. That function calls `xquik.request(path, { method, body, query })`. Authentication is injected. Use `explore` first when the agent does not know a route or parameter. Each call uses the documented Xquik route and response fields. ## Preserve the MCP Response Contract MCP returns normalized snake\_case fields and Unix-second timestamps. A REST `createdAt` field becomes `created`, not `created_at`. Preserve source values before applying an application-specific schema. Search and list responses use `has_more` and `next_cursor`. Reuse the same query and filters on every page. Pass `next_cursor` as `cursor` for tweets, profiles, followers, replies, timelines, communities, and lists. Events, draws, and extraction pages use `cursor`. Radar pages use `after`. Draft pages use `afterCursor`. Treat each cursor as opaque. Stop pagination when one condition becomes true: * The agent collects the requested total. * `has_more` becomes `false`. * `next_cursor` is missing. * `next_cursor` repeats. * The configured page cap is reached. An empty page can still have `has_more: true`. Continue when the cursor advances. De-duplicate tweets and users by their stable `id` values. MCP tool output has a 24,000-character limit. Project only the required fields. Choose extraction exports for complete-row workflows. ## Keep a Resumable Twitter Agent Handoff Conversation history is not a reliable job database. Persist the values needed for retries, pagination, exports, and downstream tools. Store `tweet_id`, `text`, `author_username`, `created`, `url`, `has_more`, `next_cursor`, and the original query. Store source `id` as `user_id`. Keep `username`, `name`, `description`, `followers`, `verified`, and `profile_picture`. Store the source user, extraction ID, status, poll URL, export state, and requested file format. Store the root tweet ID, reply IDs, parent IDs, cursor state, and coverage diagnostics. Keep nested replies separate. Store `monitor_id`, `event_id`, `type`, `occurred_at`, `has_more`, and `next_cursor`. Use the next cursor as `cursor`. Store `webhook_id`, `delivery_id`, and `stream_event_id`. Keep the one-time webhook secret in a secret manager. Store `tweet_id` or `write_action_id`. Keep `status`, `charged_credits`, `poll`, and the idempotency key. Pass public image or video URLs in `media` for tweets. Use uploaded `media_id` values only for direct messages. Keep API keys, webhook secrets, headers, and raw signatures outside agent state. Do not place private messages or full request bodies in shared traces. ## Build Twitter API Error Handling Do not let the model guess whether a failed request should retry. Match every HTTP status code before choosing the next action. A 400 Bad Request indicates invalid route parameters or unsupported fields. | Status | Meaning | LangChain agent decision | | ------ | ------------------------------------------------- | --------------------------------------------------- | | `400` | Invalid route parameters or unsupported fields | Fix the request before retrying | | `401` | Missing or invalid authentication | Stop and replace the Xquik credential | | `402` | Subscription or credit action required | Report choices and request explicit confirmation | | `424` | Upstream dependency failure or incomplete replies | Preserve partial rows and inspect `error.retryable` | | `429` | Rate limit reached | Respect `error.retry_after`, then back off | | `502` | Temporary upstream failure | Apply bounded backoff to safe read retries | Errors contain `error.type`, `error.code`, and `error.message`. Some errors add `error.retryable` or `error.retry_after`. Store these fields with the job. Preserve exact error messages for operators. Record where the error occurred. Server errors may permit bounded retries for safe reads. Client errors require a corrected request or credential. Test edge cases such as repeated cursors, expired keys, empty pages, and partial reply trees. A `402` never authorizes a purchase. Report available payment choices. Wait for an explicit decision before any supported account checkout action. ## Add Human Approval to X Actions Read-only agents can search tweets and inspect public profiles automatically. Write-enabled agents need a review boundary before posting or replying. Xquik exposes reads and writes through one aggregate `xquik` tool. Therefore, interrupt every `xquik` call in a write-capable agent. Review the proposed call before approving it. ```python theme={null} from langchain.agents import create_agent from langchain.agents.middleware import HumanInTheLoopMiddleware from langgraph.checkpoint.memory import InMemorySaver agent = create_agent( model="anthropic:claude-sonnet-4-6", tools=tools, middleware=[ HumanInTheLoopMiddleware( interrupt_on={ "xquik": { "allowed_decisions": ["approve", "reject"], } } ) ], checkpointer=InMemorySaver(), ) config = { "configurable": { "thread_id": "xquik-twitter-agent-job-001", } } pending = await agent.ainvoke( { "messages": [ { "role": "user", "content": "Draft a reply, but never post without my approval.", } ] }, config=config, ) ``` `InMemorySaver` suits local development only. Use a persistent LangGraph checkpointer in production. Resume with the same `thread_id` after approval. Reject any action with an unexpected route, account, target, text, or media. ## Build Durable LangGraph Twitter Workflows A LangGraph Twitter agent should separate discovery, review, execution, and storage. Separating these stages prevents duplicate charged actions during recovery. ```mermaid theme={null} flowchart LR A["Explore the Twitter API route"] --> B["Search tweets or load a profile"] B --> C["Validate IDs, fields, and cursor"] C --> D{"Write action needed?"} D -->|"No"| E["Persist the typed handoff"] D -->|"Yes"| F["Request human approval"] F -->|"Approve"| G["Execute once with idempotency"] F -->|"Reject"| E G --> E ``` Persist the graph after each external call. Store the last completed node, route, request fingerprint, response IDs, cursor, and retry count. Store write idempotency keys before execution. Never retry a pending write by creating a new action. Poll its returned status. Apply bounded backoff to safe reads only when the contract permits it. ## Connect Multiple MCP Servers A LangChain MCP server entry defines its transport, URL, and headers. Name each server uniquely. This keeps multiple MCP servers distinct inside the agent. Prefix tool names when another MCP server exposes similar operations. This prevents the model from choosing the wrong search or publishing tool. ```python theme={null} client = MultiServerMCPClient( { "xquik": { "transport": "http", "url": "https://xquik.com/mcp", "headers": {"x-api-key": os.environ["XQUIK_API_KEY"]}, }, "knowledge": { "transport": "http", "url": "https://knowledge.example.com/mcp", }, }, tool_name_prefix=True, ) ``` Give the Xquik agent only the tools required for its current job. Smaller tool sets improve route selection and reduce accidental actions. ## Tested LangChain Compatibility These versions were checked on August 2, 2026. | Package | Checked version | Supported range in this guide | | ------------------------ | --------------- | --------------------------------------- | | Python | 3.10 or later | `>=3.10` | | `langchain-mcp-adapters` | 0.3.0 | `>=0.3,<0.4` | | `langchain` | 1.3.14 | `>=1.3,<1.4` | | `langgraph` | 1.2.10 | Installed through LangChain constraints | Pin exact versions in production lockfiles. Re-test structured output, approval, and checkpointer behavior before upgrading a minor range. ## LangChain Twitter API Questions ### Can LangChain call the Twitter API? Yes. Load Xquik through `langchain-mcp-adapters`. The agent can call eligible tweet, profile, follower, monitor, extraction, and X action routes. ### Can LangChain scrape tweets with Python? Yes. Call `GET /api/v1/x/tweets/search` through MCP. Preserve each tweet ID, text, author, `created` timestamp, URL, and pagination cursor. ### What Does “Python API Twitter” Mean? “Python API Twitter” reverses the usual Python Twitter API search phrase. Python runs LangChain, and LangChain calls Xquik through MCP. The agent can search tweets, inspect profiles, export followers, and review X actions. ### What does LangChain MCP add to a Twitter agent? LangChain MCP converts remote operations into model-callable tools. Xquik adds tweet search, profile lookup, follower exports, monitors, and reviewed actions. ### Can LangChain post tweets through MCP? Yes. Connect the target X account first. Require human approval, preserve the idempotency key, and store the returned tweet or write-action ID. ### How do I authenticate with the Twitter API using Python? Send an Xquik API key in the MCP `x-api-key` header. X Developer keys are not required. Some private reads and writes require a connected Twitter account. ### Should I use LangChain or LangGraph for tweet search? Use LangChain for a short search and typed handoff. Use LangGraph for paginated searches, approvals, checkpoints, durable retries, or scheduled monitoring. ### How does a LangChain agent paginate Twitter results? Save `has_more` and `next_cursor`. Send the cursor with unchanged filters. Stop on completion, limits, or a stalled cursor. ### How do I prevent an agent from posting automatically? Interrupt every `xquik` tool call in write-capable agents. Approve the exact route, account, target, text, and media before execution. ### How do I post a tweet using Python with LangChain? Ask the agent to use the matching X write route. Review the exact text, account, media, and idempotency key. Approve the tool call once. ### How do I handle Twitter API rate limits in Python? Read `error.retry_after` from `429` responses. Wait for that interval. Retry safe read requests with bounded backoff. Never recreate a pending write. ### How should a Python Twitter API agent save results? Validate a Pydantic schema first. Persist typed tweet IDs, fields, cursors, errors, and job status. Never save conversational prose as JSON. ### Can a LangGraph agent export Twitter followers? Yes. Create an extraction, persist its ID, and poll its status. Export only after completion. Store the source user and requested format together. ### Can LangChain monitor Twitter keywords continuously? Yes. Create a monitor and webhook, then store their IDs. Replay missed events through `GET /api/v1/events` using `cursor` pagination. Monitor events are asynchronous. They are not guaranteed real time. ## Next Steps * Review the [MCP tool contract](/mcp/tools). * Follow the [agent handoff checklist](/mcp/agent-handoff). * Build a [tweet search workflow](/guides/tweet-scraper-csv-export). * Add [monitor and webhook delivery](/guides/brand-monitoring-workflow). * Compare [Twitter API alternatives](/twitter-api-alternatives). Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. # Make.com Twitter Integration for X API Automation Source: https://docs.xquik.com/guides/make Build Make Twitter automation for tweet search, followers, scheduled posts, webhooks, and X API errors. Connect profiles, replies, and monitors through Make.
For the complete documentation index, see llms.txt.
Xquik supports a Make.com Twitter integration without an X Developer app. Build tweet searches, profile lookups, follower exports, scheduled posts, and monitor alerts. The same integration supports replies, trends, extraction jobs, and signed webhooks. The 2025 decommissioning removed Make's native X app. A private Make custom app keeps each scenario focused and authentication testable. Teams can later decide whether public app review fits their distribution needs. ## Choose a Make Twitter Automation Pattern A Make Twitter automation should start with one clear trigger. Use schedules for recurring tweet searches, trends, or follower exports. Instant webhook delivery applies to replies, quotes, reposts, and new tweets. Use polling when an extraction job can finish after the scenario ends. Build a Make scenario automation around one bounded Xquik operation. Then route normalized tweet or profile fields to each destination. This structure keeps the automation workflow observable and easier to retry. The HTTP module supports a limited private prototype. A private custom app suits reusable actions and consistent error handling. Both approaches can call the same documented API endpoints. ## Build a Make.com API Integration A Make.com API integration uses an Xquik API key for authentication. It does not require X OAuth or a native Twitter account connection. The Make custom app stores the key inside an encrypted connection field. Use `/account` to validate API keys without changing tweets or profiles. Place the base URL, authentication header, and sanitization rules centrally. Focused modules should cover tweets, users, followers, monitors, and webhooks. A Make Twitter integration should return small, stable output bundles. Avoid passing complete API responses into Slack, Sheets, or a CRM. The Make API integration should retain IDs, cursors, timestamps, and status fields. Treat the Make Twitter API layer as a private integration boundary. The integration platform can then support drag-and-drop scenario assembly. This step-by-step structure keeps every module focused on one result. ## Prerequisites * [Xquik API key](/x-api-quickstart) * Make organization with Custom Apps access * HTTPS Make webhook URL for instant monitor-event scenarios * Optional Slack, Sheets, Airtable, or CRM module for downstream steps ## App Shape Use an API key parameter named `apiKey` and inject it as `x-api-key`. Call Xquik REST modules from `https://xquik.com/api/v1`. Start with Search Tweets, Get Tweet, Get User, Get Trends, Create Tweet, Create Extraction, Create Monitor, Create Webhook, and Make an API Call. Support Monitor Event instant webhooks and Extraction Completed polling. Map `401`, `402`, `429`, and `5xx` to short scenario messages. Make custom apps split this into a connection, base request settings, modules, and optional webhook components. Use Xquik's `/account` endpoint as the connection test because it validates the API key without mutating data. ## Connection Create a connection parameter that stores the API key as a password field: ```json theme={null} [ { "name": "apiKey", "label": "Xquik API Key", "type": "password", "required": true, "editable": true, "help": "Create an API key in the Xquik dashboard." } ] ``` Use `GET /account` to validate the connection: ```json theme={null} { "url": "https://xquik.com/api/v1/account", "method": "GET", "headers": { "x-api-key": "{{parameters.apiKey}}" }, "response": { "metadata": { "type": "email", "value": "{{body.email}}" }, "error": { "message": "{{body.message || body.error || 'Xquik authentication failed.'}}" } }, "log": { "sanitize": ["request.headers.x-api-key"] } } ``` ## Base Request Pattern Use one base request pattern for JSON modules: ```json theme={null} { "baseUrl": "https://xquik.com/api/v1", "headers": { "x-api-key": "{{connection.apiKey}}", "content-type": "application/json" }, "response": { "error": { "message": "{{body.message || body.error || 'Xquik request failed.'}}" } }, "log": { "sanitize": ["request.headers.x-api-key"] } } ``` Handle status codes consistently: Authentication failed. Check the Xquik API key. Subscription or credits required. Update billing in Xquik. Rate limited. Respect the `Retry-After` header before retrying. Xquik service unavailable. Retry with exponential backoff. ## Control Rate Limiting and Error Handling Read each endpoint contract before configuring retries. Xquik documents status codes per route, not as universal responses. * A `400` response identifies invalid request fields. The module mapping requires correction. * A `401` response identifies invalid authentication. Replace the API key. * A `402` response identifies billing requirements. The subscription requires an update first. * A `404` response identifies a missing tweet, profile, monitor, or webhook. * A `424` response identifies a dependency failure. Bounded backoff governs safe-read retries. * A `429` response identifies rate limiting. Retry timing follows the `Retry-After` header. * A `502` response identifies a temporary upstream failure. Cautious backoff governs safe-read retries. Never retry a write only because its HTTP status appears temporary. Inspect the returned `safeToRetry` field for every write action. Use a new idempotency key only when the contract permits another attempt. Make webhooks can queue bursts before a scenario processes them. Set a scenario rate limit that matches the destination's capacity. Enable sequential processing when bundle order matters. Use incomplete executions for recoverable failures that need operator review. ## Starter Modules Search module. Call `GET /x/tweets/search` with `q`; use `cursor` for page loops and keep `limit` on bounded resumes. Action module. Call `GET /x/tweets/{id}` with a tweet ID. Action module. Call `GET /x/users/{id}` with a user ID or username. Search module. Call `GET /x/trends` with optional `woeid` and `count`. Action module. Call `POST /x/tweets` with account, text, and optional public media URLs. Action module. Call `POST /extractions` with `toolType`, query fields, and result limit. Action module. Call `POST /monitors` with username and event types. Action module. Call `POST /webhooks` with callback URL and event types. Universal module. Accept any `/api/v1` path as an escape hatch for endpoints not yet modeled. Example Search Tweets module communication: ```json theme={null} { "url": "/x/tweets/search", "method": "GET", "qs": { "q": "{{parameters.q}}", "cursor": "{{parameters.cursor}}" }, "response": { "iterate": "{{body.tweets}}", "output": { "id": "{{item.id}}", "text": "{{item.text}}", "authorUsername": "{{item.author.username}}", "url": "{{item.url}}" } } } ``` Add a bounded-pull variant that sends `limit`. If `body.has_next_page` is `true`, send `body.next_cursor` as `cursor` with the same `q`, filters, and `limit`. ## Output Handoff Make response handling lets search modules `iterate` over `body.tweets` while `body` stays available for output, wrapper, and pagination fields. Emit tweet bundles from `item`, then carry setup IDs, write status, and page cursors in scenario state when downstream modules need another request. Use snake\_case keys for data-store rows even when the API response uses camelCase. Store `q`, each `tweet_id`, `text`, `author_username`, `created_at`, `has_next_page`, and `next_cursor`. Store source `id` as `user_id`, plus `username`, `name`, `followers`, `verified`, and `profile_picture`. For user-list modules, carry `has_next_page` and `next_cursor`. Store each trend `name`, `rank`, `query`, and `description`; keep `body.count`, `body.woeid`, and the selected region in scenario state. Send a unique `Idempotency-Key`. Store the returned action. Poll `status_url` while `terminal` is false. Retry only when `safe_to_retry` is true, using a new key. For tweets or replies, pass public URLs in `media` and store `tweet_id` or `write_action_id`. For DMs, upload first, pass one `media_id` in `media_ids`, store `message_id`, and leave `reply_to_message_id` unset. Store monitor `id`, `username`, `xUserId`, `eventTypes`, `isActive`, `nextBillingAt`, webhook `id`, `url`, `eventTypes`, and the one-time `secret`. For Make storage rows, map production `deliveryId` to `delivery_id` for receiver retry de-dupe and `streamEventId` to `stream_event_id` when one monitor event should process once across endpoint changes. Store `id`, `tool_type`, and `status` from `POST /extractions`; poll `GET /extractions/{id}`, then carry `has_more` and `next_cursor`. Store `deliveryId` for endpoint-level retry dedupe and `streamEventId` when one monitor event must process once across receiver changes. Call `GET /api/v1/events` with `cursor` when a scenario needs replay. Map `id`, `monitorId`, `monitorType`, `occurredAt`, `hasMore`, and `nextCursor` to `event_id`, `monitor_id`, `monitor_type`, `occurred_at`, `has_more`, and `next_cursor`. Return `2xx` after accepting duplicate `deliveryId` or `streamEventId`; keep endpoint signing values, raw request body, raw signature, and full headers out of scenario logs, data stores, Slack messages, CRM rows, and retry queues. ## Instant Trigger: Monitor Events Use a dedicated Make webhook for monitor events. Register that webhook URL in Xquik: ```json theme={null} { "url": "https://xquik.com/api/v1/webhooks", "method": "POST", "body": { "url": "{{webhook.url}}", "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote", "tweet.retweet"] } } ``` Then create or confirm the monitor: ```json theme={null} { "url": "https://xquik.com/api/v1/monitors", "method": "POST", "body": { "username": "username", "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote", "tweet.retweet"] } } ``` Map webhook output fields for downstream modules: Map `eventType` to route `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet` events. Map `deliveryId` as the per-endpoint idempotency key for retries. Map `streamEventId` when one monitor event should process once across endpoint changes. Map `occurredAt` as the event timestamp. Map `username` for account monitor events. Map `data.id` as the tweet identifier. Map `data.text` as the tweet body. Map `data.author.userName` when present. Use `username` as the monitored-account fallback. Keep the webhook secret returned by Xquik. If the scenario includes a verification step before routing, verify `x-xquik-signature` with that secret before sending alerts. ## Polling Trigger: Extraction Completed Use a polling trigger when users want bulk results without webhook setup: ```json theme={null} { "url": "/extractions", "method": "GET", "qs": { "status": "completed", "limit": 25 }, "response": { "iterate": "{{body.extractions}}", "uid": "{{item.id}}", "output": { "id": "{{item.id}}", "status": "{{item.status}}", "toolType": "{{item.toolType}}", "createdAt": "{{item.createdAt}}", "completedAt": "{{item.completedAt}}" } } } ``` Fetch the job detail with `GET /extractions/{id}` and loop through `nextCursor` when `hasMore` is true. ## Recipes ### Social Listening To Slack Start from the Xquik Monitor Event instant trigger for `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet`. Filter on `eventType`, `username`, and `data.text` before routing alerts. Create a Slack message from `data.text`, `data.id`, `data.author.userName`, and `occurredAt`. Upsert by `deliveryId` per endpoint. Use `streamEventId` when one monitor event should fan out once across endpoint changes. ### Daily Topic Research To Sheets Run the scenario on a daily schedule for repeatable topic research. Call Xquik Search Tweets with `q`; use `cursor` for page loops and keep `limit` on bounded resumes. Iterate over `tweets` and pass one tweet bundle to each downstream module. Append `id`, `author.username`, `text`, `createdAt`, `likeCount`, and `retweetCount`. ### Bulk Extraction To CRM Use a scheduler or manual trigger to start the bulk extraction. Call Xquik Create Extraction with `toolType` and the required target fields. Wait before polling, or reuse the Extraction Completed polling trigger. Call `GET /extractions/{id}` until `job.status` is `completed` or `failed`. Upsert by user `id`, then follow `hasMore` and `nextCursor` for additional result pages. ## Automate Focused Twitter Workflows Tweet search automation can collect posts matching a brand or topic query. Filter by author, timestamp, language, or engagement before sending alerts. Store tweet IDs so repeated searches do not create duplicate messages. A Twitter monitor webhook can route new tweets and replies immediately. Verify its signature before parsing the request body. Deduplicate deliveries before posting to Slack or updating a CRM. Use follower pages for bounded exports to Google Sheets. Use extraction jobs for larger follower or following exports. Preserve profile IDs because usernames can change. Social media automation should keep publishing under human control. The approval queue governs every scheduled tweet and reply. Review text, links, media, and the target account before posting tweets. Do not automate unsolicited replies, follows, or direct messages. ## Make.com Twitter Integration Questions ### How Do I Connect the Twitter API to Make? A private Make custom app or HTTP module provides the integration path. Authenticate each Xquik request with the `x-api-key` header. Start with one read endpoint before adding writes or webhooks. ### Does Make.com Still Have a Native Twitter Integration? The native X app became unavailable in Make during 2025. Xquik remains available through a private app or the Make HTTP module. This approach avoids a native Twitter integration dependency. ### How Can I Schedule Tweets Automatically? Create a scheduled scenario that prepares one draft payload. Send the draft through an approval step before publishing. Use an idempotency key and inspect the returned write status. ### How Do I Build a Twitter Webhook in Make? The workflow requires a Make webhook URL registered with Xquik. Choose the required monitor event types during webhook registration. Verify signatures, reject stale requests, and deduplicate event IDs. ### Can Make Automate Twitter Search Without Coding? Yes. Install the private Xquik app, then configure its search module. Map the query, filters, limit, and cursor through the scenario builder. The scenario can append matching tweets to Sheets without custom code. ### How Do I Automate Replies Based on Keywords? Search recent replies or receive monitored account events. Filter each tweet with explicit terms and safety rules. Send the proposed reply to an approval queue before publishing. ### How Do I Connect Twitter Activity to a CRM? Receive a verified monitor event, then fetch its author profile. The CRM upsert uses the immutable X user ID. The CRM record retains the triggering tweet URL with the profile fields. ### Which Twitter Automation Tool Fits Make Scenarios? Use Xquik when scenarios need tweets, profiles, followers, or monitor webhooks. Use Make for scheduling, routing, approvals, and destination integrations. Together, they form a focused Twitter API integration. ## Make and Xquik Sources * [Make custom app base configuration](https://developers.make.com/custom-apps-documentation/app-components/base) * [Make connection validation guidance](https://developers.make.com/custom-apps-documentation/best-practices/connections) * [Make community decommissioning notice](https://community.make.com/t/make-is-officially-decommissioning-x-formerly-twitter-app/77497) * [Make webhook documentation](https://help.make.com/webhooks) * [Make scenario scheduling](https://help.make.com/schedule-a-scenario) * [Xquik webhook verification](/webhooks/overview) ## Test Checklist * Connection test rejects invalid API keys with a clear `401` message. * Every module sanitizes `x-api-key` in logs. * Search modules return arrays and stable IDs for Make deduplication. * The instant trigger must map `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet`. * Extraction polling stops when no new completed jobs are returned. * A `429` test confirms that retry timing follows `Retry-After`. * The Universal module accepts any `/api/v1` path but still injects the API key. ## Next Steps * Read [Webhooks](/webhooks/overview) for payload shape and retries. * Read [Extraction Workflow](/guides/extraction-workflow) for job creation and pagination. * Use [Zapier](/guides/zapier) or [Pipedream](/guides/pipedream) when the team prefers code-backed workflow components. # Mastra AI Twitter MCP Agent Guide for TypeScript Source: https://docs.xquik.com/guides/mastra Build a Mastra Twitter agent for tweet search, followers, monitors, and approved X actions. Add typed MCP workflows, pagination, errors, and resumable handoffs.
For the complete documentation index, see llms.txt.
Build a Mastra AI Twitter MCP agent through Xquik's remote MCP server. This Mastra Twitter agent searches tweets, profiles, replies, followers, and monitors. It preserves every tweet ID, profile ID, cursor, and job ID. This Mastra AI tutorial combines agent tools, one typed workflow, and a strict handoff. Xquik provides the Twitter MCP server and documented API endpoints. Mastra controls the AI model, tool call, approval, and agent workflows. ## Why Use the Mastra AI Agent Framework With a Twitter API? Mastra is an open-source TypeScript framework for building AI applications. The Mastra AI agent framework adds tools, routing, memory, and workflows. Use an X API agent when a model must choose related operations. Use REST for fixed routes, scheduled exports, or predictable latency. | Boundary | Mastra control | Twitter agent result | | -------------- | ---------------------- | --------------------------------------------------- | | Remote MCP | `MCPClient` | Connect tweet, profile, follower, and monitor tools | | Final response | Zod `structuredOutput` | Reject malformed rows and cursor state | | Per-call tools | `listToolsets()` | Isolate each user's Xquik key | | X actions | `requireToolApproval` | Pause before the `xquik` tool runs | | Tool errors | `onToolError: "throw"` | Preserve failed MCP results | | Cleanup | `disconnect()` | Close remote sessions | ## Mastra Twitter API Prerequisites * Node.js 22.13 or later * An [Xquik API key](/x-api-quickstart) beginning with `xq_` * A model-provider key supported by Mastra * A connected X account for private reads or writes Public reads need no X Developer credentials. Authenticate through Xquik. ## Install the Mastra AI SDK and MCP Support Install the tested Mastra AI SDK packages and Zod. ```bash theme={null} npm install "@mastra/core@^1.55" "@mastra/mcp@^1.15" zod ``` Store secrets outside source control. ```bash .env theme={null} XQUIK_API_KEY=xq_YOUR_KEY_HERE OPENAI_API_KEY=YOUR_OPENAI_KEY ``` ```text .gitignore theme={null} .env xquik-*-handoff.json ``` Run `npm run dev` in a generated Mastra project. Then inspect agent calls in Mastra Studio. ## Build a Mastra AI Agent Example for Tweet Search Define the final handoff before creating the agent. ```typescript theme={null} import { writeFile } from "node:fs/promises"; import { Agent } from "@mastra/core/agent"; import { createStep, createWorkflow } from "@mastra/core/workflows"; import { MCPClient } from "@mastra/mcp"; import { z } from "zod"; const tweetRowSchema = z.object({ tweet_id: z.string(), text: z.string(), author_username: z.string().nullable(), created: z.number().int().nullable(), url: z.string().url().nullable(), }); const handoffSchema = z.object({ query: z.string(), route_used: z.literal("GET /api/v1/x/tweets/search"), tweets: z.array(tweetRowSchema), has_more: z.boolean(), next_cursor: z.string().nullable(), pages_fetched: z.number().int().positive(), stop_reason: z.enum([ "complete", "requested_limit", "cursor_stalled", ]), }); const mcp = new MCPClient({ servers: { xquik: { url: new URL("https://xquik.com/mcp"), requestInit: { headers: { "x-api-key": process.env.XQUIK_API_KEY!, }, }, onToolError: "throw", }, }, timeout: 60_000, }); try { const agent = new Agent({ id: "xquik-tweet-search-agent", name: "Xquik Tweet Search Agent", instructions: [ "Use Xquik for Twitter API requests.", "Use GET /api/v1/x/tweets/search.", "Preserve exact tweet IDs, created timestamps, and cursors.", "Never invent missing tweet fields.", "Stop when the requested limit is reached.", "Stop if the server repeats a cursor.", ].join(" "), model: "openai/gpt-5", tools: await mcp.listTools(), }); const searchStep = createStep({ id: "search-twitter", inputSchema: z.object({ query: z.string().min(1) }), outputSchema: handoffSchema, execute: async ({ inputData }) => { const result = await agent.generate( `Search 50 latest tweets for: ${inputData.query}`, { structuredOutput: { schema: handoffSchema } }, ); return handoffSchema.parse(result.object); }, }); const tweetResearchWorkflow = createWorkflow({ id: "xquik-tweet-research", inputSchema: z.object({ query: z.string().min(1) }), outputSchema: handoffSchema, }) .then(searchStep) .commit(); const run = await tweetResearchWorkflow.createRun(); const workflowResult = await run.start({ inputData: { query: "Mastra AI MCP" }, }); if (workflowResult.status !== "success") { throw new Error(`Tweet workflow ended with ${workflowResult.status}.`); } await writeFile( "xquik-mastra-handoff.json", JSON.stringify(workflowResult.result, null, 2), "utf8", ); } finally { await mcp.disconnect(); } ``` `listTools()` suits static agent construction. `listToolsets()` groups tools by server for each call. The client tries Streamable HTTP for URL servers. The MCP runtime returns normalized snake\_case fields through `xquik.request()`. It normalizes `createdAt` to the Unix-second field `created`. Keep the Zod schema aligned with that contract. ## Run a Mastra AI Workflow for Tweet Research The `inputSchema: z.object(...)` declaration validates every query. The `outputSchema` protects the final handoff. Mastra core workflows chain steps with `.then()` and finish with `.commit()`. `createRun()` creates isolated state. Store each successful handoff immediately. A registered agent can also run the workflow through a Mastra instance. ## Search Tweets With Precise Operators Keep the exact `q` in every checkpoint. | Search intent | Example `q` | | ------------------------ | -------------------------------------------------- | | Framework posts | `"Mastra" MCP` | | One timeline | `from:mastra_ai since:2026-07-01 until:2026-08-01` | | Popular TypeScript posts | `"Twitter API" TypeScript lang:en min_faves:25` | | Questions | `"tweet search agent" ? -filter:retweets` | Use `queryType=Latest` for time-ordered research. Pass `next_cursor` unchanged. Stop when `has_more` becomes false. Also stop when a cursor repeats. ## Require Approval for Twitter Actions Xquik exposes one `xquik` execution tool. It can run authorized reads and writes. Require human-in-the-loop approval when the key permits writes. ```typescript theme={null} const approvedMcp = new MCPClient({ servers: { xquik: { url: new URL("https://xquik.com/mcp"), requestInit: { headers: { "x-api-key": process.env.XQUIK_API_KEY! }, }, requireToolApproval: ({ toolName }) => toolName === "xquik", onToolError: "throw", }, }, }); ``` Show the route, method, arguments, and X account before approval. Never treat MCP annotations as an authorization boundary. They are server-provided hints. `@mastra/core` 1.55.0 predates the declined-call fix in [PR #20487](https://github.com/mastra-ai/mastra/pull/20487). Do not enable writes on that release. Upgrade after a stable fix. Then prove declined calls never execute. Use a guest `paid_reads` key for a firm read-only boundary. See [guest wallets](/guides/guest-wallets) for its exact scope. ## Expose Discovery Without Execution Filter `listTools()` when an agent should only inspect endpoint schemas. ```typescript theme={null} const tools = await mcp.listTools(); const discoveryTools = Object.fromEntries( Object.entries(tools).filter(([name]) => name.endsWith("_explore")), ); const discoveryAgent = new Agent({ id: "xquik-route-discovery-agent", name: "Xquik Route Discovery Agent", instructions: "Inspect Twitter API routes. Never execute an operation.", model: "openai/gpt-5", tools: discoveryTools, }); ``` The `explore` tool returns routes, methods, parameters, and response fields. It does not call X. Adding `xquik` enables every key-authorized operation. ## Use a Mastra AI MCP Client for Each User Create one client after resolving the tenant identity. ```typescript theme={null} import { Agent } from "@mastra/core/agent"; import { MCPClient } from "@mastra/mcp"; async function runTenantRequest(prompt: string, userApiKey: string) { const tenantMcp = new MCPClient({ id: crypto.randomUUID(), servers: { xquik: { url: new URL("https://xquik.com/mcp"), requestInit: { headers: { "x-api-key": userApiKey }, }, requireToolApproval: true, onToolError: "throw", }, }, }); const agent = new Agent({ id: "tenant-twitter-agent", name: "Tenant Twitter Agent", instructions: "Preserve tweet IDs, profile IDs, cursors, and job IDs.", model: "openai/gpt-5", }); try { return await agent.generate(prompt, { toolsets: await tenantMcp.listToolsets(), }); } finally { await tenantMcp.disconnect(); } } ``` Never share keys across tenants. Keep keys outside prompts, traces, and handoffs. Supply a unique client `id` for otherwise identical settings. ## Forward Dynamic Headers Safely Use custom `fetch` when request context selects a tenant key. ```typescript theme={null} const mcp = new MCPClient({ servers: { xquik: { url: new URL("https://xquik.com/mcp"), fetch: async (url, init) => { const headers = new Headers(init?.headers); headers.set("x-api-key", await secretStore.getXquikKey()); return fetch(url, { ...init, headers }); }, onToolError: "throw", }, }, }); ``` The `secretStore` object represents your existing secret manager. Never send its key to the model. Keep `forwardInstructions` disabled for untrusted servers. ## Store a Resumable Twitter Agent Handoff Store validated identifiers outside the model transcript. Store `tweet_id`, `text`, `author_username`, `created`, `url`, `has_more`, `next_cursor`, and the original `q`. A Twitter follower scraper API handoff keeps `user_id`, `username`, `followers`, `has_more`, and `next_cursor`. Store `monitor_id`, `event_id`, `type`, `occurred_at`, and replay cursors. Store `extraction_id`, `status`, `poll`, and `export_after_complete`. Store `tweet_id`, `write_action_id`, `status`, and `poll`. Never resend pending writes. Store `webhook_id`, `delivery_id`, and `stream_event_id`. Protect the secret. Persist the validated `result.object`, not free-form `result.text`. ## Handle Twitter API Errors and Rate Limits Match each documented status before retrying. | Status | Meaning | Agent action | | ------ | ------------------------------------------------- | ---------------------------- | | `400` | The search query is missing or invalid | Fix `q` | | `401` | Guest authentication cannot complete this request | Provide a key or connect X | | `402` | The account lacks credits | Request account action | | `424` | The upstream X dependency failed | Apply bounded backoff | | `429` | The Twitter API rate limit applies | Preserve the cursor and wait | | `502` | The X dependency returned an invalid response | Retry later | Keep `onToolError: "throw"`. It preserves MCP `isError` failures. Never restart pagination after `429`. Read each route before designing retry logic. ## Verify Mastra Package Compatibility These stable versions were checked on August 3, 2026. | Package | Checked version | Requirement | | --------------------------- | --------------- | ------------------------ | | `@mastra/core` | 1.55.0 | Node.js 22.13 or later | | `@mastra/mcp` | 1.15.0 | `@mastra/core` below 2.0 | | `@modelcontextprotocol/sdk` | 1.30.0 | Installed through Mastra | | `zod` | 4.4.3 | `^3.25` or `^4` | `@mastra/mcp` 1.15.0 predates the concurrent reconnect fix in [PR #20530](https://github.com/mastra-ai/mastra/pull/20530). Avoid parallel recovery through one client. Test reconnects before every upgrade. ## Mastra AI MCP Frequently Asked Questions ### What Are the Main Mastra AI MCP Features? The integration combines typed agent tools, workflows, approval, and MCP. It supports tweets, profiles, followers, monitors, webhooks, and writes. ### How Does Mastra AI MCP Compare With Other Agent Frameworks? Choose Mastra for TypeScript-first agent workflows and Zod contracts. Choose direct REST for fixed routes. Xquik supports both paths. ### What Makes a Robust Twitter Agent System? Require bounded queries, typed outputs, exact IDs, cursor checkpoints, and rate limiting. Isolate credentials and approve every write. ### Which Mastra AI Examples Does This Tutorial Include? It covers MCP discovery, tweet search, workflows, approval, errors, tenant isolation, and durable handoffs. ### What Is a Mastra AI MCP Client? The Mastra AI MCP client uses Xquik's remote tools. A Mastra AI MCP server publishes local tools. ### How Do I Search Tweets With TypeScript? Call `GET /api/v1/x/tweets/search`. Supply `q`, `queryType`, and `limit`. Preserve `tweet_id`, `created`, and `next_cursor`. ### Can a Mastra Agent Export Twitter Followers? The Twitter follower scraper API returns profiles and cursors. Use the [followers API](/api-reference/x/followers) for bounded pages. Use extraction jobs for CSV, JSON, or XLSX exports. ### Can Mastra Monitor Tweets in Real Time? Monitors store matching events between agent calls. Replay events by cursor. Use signed webhooks when the receiver needs immediate delivery. ### Can a Mastra AI Agent Post Tweets and Replies? Yes, after connecting X. Do not enable writes on affected Mastra releases. Test approval rejection before production. ### How Should a Mastra Agent Handle Twitter Rate Limits? Treat `429` separately from dependency failures. Save the cursor and completed tweet IDs. Resume after reset guidance. # Twitter Media Uploads for Tweets & DMs | Tweet API Source: https://docs.xquik.com/guides/media-upload-workflow Upload images, GIFs, WebP, AVIF, or MP4 by file or URL. Use mediaUrl for tweets and replies, or mediaId for one DM attachment. Includes exact API steps.
For the complete documentation index, see llms.txt.
Use this workflow when a product, support, CRM, or AI agent system needs a hosted media URL for tweet posts or an uploaded media ID for direct messages. Xquik accepts local files with `multipart/form-data` and HTTPS media URLs with `application/json`. If you already have public image URLs or a public MP4 video URL for a tweet or reply, skip `POST /x/media` and pass those URLs directly in the `media` array on `POST /x/tweets`. Send up to 4 images or exactly 1 MP4 video up to 100 MB. Use `POST /x/media` when you need Xquik to host a local file, validate a generated media URL, or produce a `mediaId` for a DM attachment. **Need to post an MP4 tweet?** If the MP4 is already a public HTTPS URL, skip upload and call [Create Tweet](/api-reference/x-write/create-tweet) with `media: ["https://example.com/video.mp4"]`. Use `POST /x/media` first only when Xquik must host a local file or validate a generated URL, then pass the returned `mediaUrl` to `POST /x/tweets`. ## When to use this workflow Upload a local image, GIF, or MP4 with `POST /x/media` and `multipart/form-data`. Upload AI-generated media from a URL with `POST /x/media` and `application/json`. Pass the URLs directly in the `media` array on `POST /x/tweets`. Pass returned `mediaUrl` in the `media` array on `POST /x/tweets`. Pass `mediaUrl` plus `reply_to_tweet_id` on `POST /x/tweets`. Pass returned `mediaId` as the only item in `media_ids` on `POST /x/dm/{userId}`. ## Data you get `mediaId` is the uploaded media ID for one-item DM `media_ids` arrays. Media IDs are valid for 24 hours after upload. `mediaUrl` is the public media URL for tweet `media` arrays. `success` is `true` after upload completes. `tweetId` is returned by `POST /x/tweets` after the media tweet or reply is confirmed. ## End-to-end media handoff Use one checkpoint object after upload and the downstream tweet, reply, or DM write. Keep the field boundary explicit: tweets and replies use public `mediaUrl` values in `media`; DMs use the uploaded `mediaId` as the only `media_ids` item. ```json theme={null} { "workflow": "media_upload_handoff", "upload": { "endpoint": "/api/v1/x/media", "account": "myxhandle", "source_type": "url", "source": "https://example.com/image.png", "media_id": "1893726451023847424", "media_url": "https://media.xquik.com/uploads/1893726451023847424.png", "media_id_expires_in_hours": 24, "success": true }, "tweet_post": { "endpoint": "/api/v1/x/tweets", "account": "myxhandle", "media": ["https://media.xquik.com/uploads/1893726451023847424.png"], "tweet_id": "1895432178065391234", "success": true, "charged_credits": "32" }, "reply_post": { "endpoint": "/api/v1/x/tweets", "account": "myxhandle", "reply_to_tweet_id": "1893704267862470862", "media": ["https://media.xquik.com/uploads/1893726451023847424.png"], "tweet_id": "1895432178065391235", "write_status": "posted" }, "dm_send": { "endpoint": "/api/v1/x/dm/44196397", "account": "myxhandle", "recipient_user_id": "44196397", "media_ids": ["1893726451023847424"], "message_id": "1893726451029384192", "success": true }, "field_boundary": { "tweet_media_field": "media", "tweet_media_value": "mediaUrl", "dm_media_field": "media_ids[0]", "dm_media_value": "mediaId", "forbidden_tweet_field": "media_ids" }, "audit_row": { "record_type": "media_upload_handoff", "source_endpoint": "/api/v1/x/media", "account": "myxhandle", "media_id": "1893726451023847424", "media_url": "https://media.xquik.com/uploads/1893726451023847424.png", "tweet_id": "1895432178065391234", "message_id": "1893726451029384192", "handoff_format": "jsonl" }, "handoff_state": "store_media_url_for_tweets_and_media_id_for_dms" } ``` Store returned `mediaId`, `mediaUrl`, `success`, source URL or filename, and the upload endpoint. Store the public `mediaUrl` in `media`, plus returned `tweetId`, `chargedCredits`, and optional `writeActionId`. Store `reply_to_tweet_id`, public `mediaUrl`, returned `tweetId`, and pending confirmation state when present. Store exactly one uploaded `mediaId` in `media_ids`, plus returned `messageId` and recipient ID. Reject tweet handoffs that put uploaded media IDs in `media` or send `media_ids` to `POST /x/tweets`. Store upload, tweet, reply, and DM IDs together so downstream systems can reconcile each media path. ## Step 1: Upload media by URL Use JSON URL upload when an AI agent, MCP client, or workflow tool has a generated image or MP4 URL that Xquik should validate and host. For tweet-only workflows with already public image URLs or exactly 1 public MP4 video URL up to 100 MB, call `POST /x/tweets` directly with `media`. The URL must use HTTPS, resolve to a public address, return a supported media content type, finish within 30 seconds, and stay under the 15,728,640-byte URL download cap. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/media \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "url": "https://example.com/image.png" }' | jq ``` ```json theme={null} { "mediaId": "1893726451023847424", "mediaUrl": "https://media.xquik.com/uploads/1893726451023847424.png", "success": true } ``` ### URL upload checklist Non-HTTPS URLs return `422 media_download_failed`. Private or reserved IP targets are rejected. AVIF, GIF, JPEG, PNG, WebP, and MP4 are accepted. Larger URL downloads than 15,728,640 bytes return `422 media_download_failed`. Slow origins can time out before upload starts. ## Step 2: Post a tweet or reply with mediaUrl `POST /x/tweets` accepts public media URLs in `media`. Send up to 4 images or exactly 1 MP4 video up to 100 MB. Use the `mediaUrl` returned by `POST /x/media`. Text-only tweet or reply writes cost 30 credits; attached media adds 2 credits per started MB across all files. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/tweets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "text": "Product update with a new screenshot.", "media": ["https://media.xquik.com/uploads/1893726451023847424.png"] }' | jq ``` To post a media reply, send the same `media` URL array and add `reply_to_tweet_id`. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/tweets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "text": "Here is the requested screenshot.", "reply_to_tweet_id": "1893704267862470862", "media": ["https://media.xquik.com/uploads/1893726451023847424.png"] }' | jq ``` ```json theme={null} { "tweetId": "1895432178065391234", "success": true } ``` Store the action `id`, `request.hash`, `billing`, `result`, original `mediaUrl`, and parent `reply_to_tweet_id`. Poll [Get write action status](/api-reference/x-write/get-write-action-status) while `terminal` is `false`. Retry only when `safeToRetry` is `true`, using a new `Idempotency-Key`. Do not send `media_ids` to `POST /x/tweets`. That endpoint rejects `media_ids` and expects the `media` URL array instead. ## Step 3: Send a DM with mediaId `POST /x/dm/{userId}` accepts one uploaded media ID in `media_ids`. Use the `mediaId` returned by `POST /x/media`. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/dm/44196397 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "myxhandle", "text": "Here is the requested image.", "media_ids": ["1893726451023847424"] }' | jq ``` DMs accept exactly one uploaded media item. Send no `media_ids` field for text-only DMs. Store `message_id`, `media_id`, recipient, account, and send status in shared handoff rows. Keep full DM body text in private systems only. ### JSON Lines handoff For queues, CRM syncs, warehouse loads, or agent memory, write one record per upload and downstream write to `xquik-media-handoff.jsonl`. #### Media upload row ```json theme={null} { "record_type": "media_upload", "account": "myxhandle", "source": "https://example.com/image.png", "media_id": "1893726451023847424", "media_url": "https://media.xquik.com/uploads/1893726451023847424.png", "media_id_expires_in_hours": 24, "tweet_media_field": "media", "dm_media_field": "media_ids[0]", "handoff_format": "jsonl" } ``` #### Tweet or reply row ```json theme={null} { "record_type": "tweet_media_post", "account": "myxhandle", "tweet_id": "1895432178065391234", "reply_to_tweet_id": "1893704267862470862", "media_url": "https://media.xquik.com/uploads/1893726451023847424.png", "write_status": "posted", "handoff_format": "jsonl" } ``` #### DM media row ```json theme={null} { "record_type": "dm_media_send", "account": "myxhandle", "recipient_user_id": "44196397", "message_id": "1893726451029384192", "media_id": "1893726451023847424", "write_status": "sent", "handoff_format": "jsonl" } ``` Use `media_url` for tweet and reply `media` arrays. Use `media_id` for the single DM `media_ids` item. ## Step 4: Upload a local file Use multipart upload when your app has the file bytes. Supported formats are AVIF, GIF, JPEG, PNG, WebP, and MP4. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/media \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -F "account=myxhandle" \ -F "file=@/path/to/image.png" | jq ``` For MP4 files longer than 140 seconds, add `is_long_video=true`. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/media \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -F "account=myxhandle" \ -F "file=@/path/to/video.mp4" \ -F "is_long_video=true" | jq ``` ## Cost and error handling Upload media costs 10 credits per upload call. Posting a tweet or reply is a separate 30-credit create-tweet call before media surcharges; sending the DM is a separate 10-credit write call. Status `400`. Error `invalid_input`. Check `account`, file type, file presence, URL presence, and `is_long_video`. Status `402`. Errors `no_subscription` or `insufficient_credits`. Subscribe or top up credits before retrying. Status `403`. Error `account_needs_reauth`. Reconnect the X account from the dashboard. Status `422`. Error `media_download_failed`. Use an HTTPS URL that returns a supported media file, or switch to multipart upload. Status `429` or `503`. Retry with exponential backoff and respect `Retry-After` when present. ## Handoff checklist Save `mediaUrl` and pass it to `POST /x/tweets` as `media`. Save the parent tweet ID, `mediaUrl`, returned `tweetId`, and any `writeActionId`. Save `mediaId` and pass it to `POST /x/dm/{userId}` as one `media_ids` item. Store upload, tweet/reply, or DM handoff rows in `xquik-media-handoff.jsonl` with `media_id` and `media_url`. Use JSON URL upload only when the agent needs Xquik to validate or host a generated URL, or needs a DM `mediaId`. For tweet-only public URLs, pass them directly to `POST /x/tweets`. Prefer multipart upload when your app owns the file bytes. **Related:** [Upload Media](/api-reference/x-write/upload-media) · [Create Tweet](/api-reference/x-write/create-tweet) · [Send Direct Message](/api-reference/x-write/send-dm) # Microsoft Agent Framework Twitter MCP Python Guide Source: https://docs.xquik.com/guides/microsoft-agent-framework Build a Microsoft Agent Framework Twitter MCP agent for tweet search, profiles, followers, monitors, exports, and approved X actions with typed Python output.
For the complete documentation index, see llms.txt.
Use this Microsoft Agent Framework tutorial to build a production-ready Twitter MCP agent. This Microsoft Agent Framework Python example connects to Xquik's remote MCP server. Search tweets, inspect profiles, export followers, and replay monitors. Review X actions before execution. Preserve every tweet ID, profile ID, cursor, and job identifier exactly. Connect the Microsoft Agent Framework MCP server client through Streamable HTTP. ## Why Use the Microsoft AI Agent Framework With a Twitter API? The Microsoft open-source agent framework provides agents, sessions, tools, middleware, and workflows. It supports Python and .NET. Use this Microsoft AI agent framework when Microsoft chat clients fit your stack. These building blocks support single-agent tasks and multi-agent systems. Xquik publishes Twitter API operations through two MCP tools: `explore` and `xquik`. Use each boundary for one responsibility. | Boundary | Framework control | Twitter agent benefit | | -------------- | ------------------------------- | -------------------------------------------------------------- | | Remote MCP | `MCPStreamableHTTPTool` | Connect agents to tweet, profile, follower, and monitor routes | | Final response | Pydantic `response_format` | Reject malformed tweet rows and pagination state | | Tenant context | `header_provider` | Resolve one Xquik key for each agent run | | X actions | MCP `approval_mode` | Pause before the shared `xquik` execution tool runs | | Agent handoff | `AgentSession` and typed models | Preserve IDs without copying whole transcripts | | Tool scope | `allowed_tools` | Expose discovery alone or both MCP tools | Choose it for Python services requiring model-selected tool calls. Choose direct REST for fixed jobs without model choices. ## Microsoft Agent Framework Getting Started * Python 3.10 or later * An [Xquik API key](/x-api-quickstart) beginning with `xq_` * An LLM provider key supported by Microsoft Agent Framework * Connect an X account for private reads or X write actions Public X reads do not require X Developer credentials. Authenticate with Xquik. Connect an X account only when the route requires it. Keep provider keys and Xquik keys in separate environment variables. Version 1.13.0 protects initialization requests and later tool invocations. Pin it when building AI agents with tenant credentials. Choose only AI models supported by the configured chat client. ## Install Microsoft Agent Framework MCP Support Install the tested Python release. ```bash theme={null} python -m pip install "agent-framework==1.13.0" ``` Store secrets outside source control. ```bash .env theme={null} XQUIK_READ_ONLY_API_KEY=xq_YOUR_PAID_READS_KEY XQUIK_API_KEY=xq_YOUR_WRITE_CAPABLE_KEY OPENAI_API_KEY=YOUR_OPENAI_KEY OPENAI_CHAT_MODEL=gpt-5 ``` ```text .gitignore theme={null} .env xquik-*-handoff.json ``` ## Microsoft Agent Framework Python Example for Tweet Search Define the handoff before creating the agent. This production-ready example uses Model Context Protocol (MCP) tool calls. The framework parses the final response into the Pydantic model. Use a guest `paid_reads` key for this autonomous example. Never give an unattended research agent a write-capable key. ```python theme={null} import asyncio import os from pathlib import Path from typing import Literal from agent_framework import Agent, MCPStreamableHTTPTool from agent_framework.openai import OpenAIChatClient from pydantic import BaseModel class TweetRow(BaseModel): tweet_id: str text: str author_username: str | None created: int | None url: str | None class TweetSearchHandoff(BaseModel): query: str route_used: Literal["GET /api/v1/x/tweets/search"] tweets: list[TweetRow] has_more: bool next_cursor: str | None pages_fetched: int stop_reason: Literal[ "complete", "requested_limit", "cursor_stalled", ] async def main() -> None: xquik_mcp = MCPStreamableHTTPTool( name="xquik-twitter-api", url="https://xquik.com/mcp", description="Search tweets, profiles, followers, monitors, and exports.", allowed_tools=["explore", "xquik"], header_provider=lambda kwargs: { "x-api-key": kwargs["xquik_api_key"], }, ) async with Agent( client=OpenAIChatClient(model=os.environ["OPENAI_CHAT_MODEL"]), name="xquik_tweet_search_agent", instructions=( "Use GET /api/v1/x/tweets/search. " "Preserve exact tweet IDs, created timestamps, and cursors. " "Never invent missing tweet fields. " "Stop at the requested limit. Stop if a cursor repeats." ), tools=xquik_mcp, ) as agent: result = await agent.run( "Search 50 latest tweets about Microsoft Agent Framework MCP.", options={"response_format": TweetSearchHandoff}, function_invocation_kwargs={ "xquik_api_key": os.environ["XQUIK_READ_ONLY_API_KEY"], }, ) if not isinstance(result.value, TweetSearchHandoff): raise RuntimeError("Tweet handoff invalid. Inspect the agent response.") Path("xquik-microsoft-agent-handoff.json").write_text( result.value.model_dump_json(indent=2), encoding="utf-8", ) asyncio.run(main()) ``` `allowed_tools` keeps the remote surface explicit. The agent receives only `explore` and `xquik` from Xquik's MCP server. The agent framework import loads `Agent` and `MCPStreamableHTTPTool`. Agent framework agents use only the selected Xquik MCP tools. `response_format` asks the model for the Pydantic shape. Read the validated object from `result.value`. Never persist `result.text` as a typed handoff. The MCP runtime returns normalized snake\_case fields through `xquik.request()`. It normalizes `createdAt` to the Unix-second field `created`. Keep the Pydantic model aligned with that contract. ## Search Tweets With Focused Queries Send a precise `q` to the tweet search route. Keep it in the handoff. | Search intent | Example `q` | | ------------------------- | -------------------------------------------------- | | Framework discussions | `"Microsoft Agent Framework" MCP` | | One account's timeline | `from:Microsoft since:2026-07-01 until:2026-08-01` | | Popular Python posts | `"Twitter API" Python lang:en min_faves:25` | | Questions without reposts | `"tweet search agent" ? -filter:retweets` | | Customer support mentions | `@brand (help OR issue) -filter:retweets` | | Exact tweet | A numeric Tweet ID or X status URL | Use `queryType=Latest` for chronological monitoring. Use `Top` for engagement-ranked research. See the [tweet search API contract](/api-reference/x/search-tweets). Pass `next_cursor` back unchanged. Never rebuild a cursor. Stop when `has_more` becomes false. Also stop when a cursor repeats. Continue through an empty page when `has_more` remains true. Deduplicate resumed results by `tweet_id`. ## Add Human-in-the-Loop Approval for Twitter Actions Xquik exposes one `xquik` execution tool. It runs routes authorized by the API key. Tool-name checks cannot distinguish routes inside `xquik`. Apply approval to `xquik` whenever a key authorizes writes. Ask a human in the loop to inspect every write-capable request. ```python theme={null} from agent_framework import Agent, MCPStreamableHTTPTool, Message approved_mcp = MCPStreamableHTTPTool( name="xquik-twitter-api", url="https://xquik.com/mcp", allowed_tools=["explore", "xquik"], approval_mode={ "never_require_approval": ["explore"], "always_require_approval": ["xquik"], }, header_provider=lambda kwargs: { "x-api-key": kwargs["xquik_api_key"], }, ) async with Agent( client=OpenAIChatClient(model=os.environ["OPENAI_CHAT_MODEL"]), name="approved_xquik_agent", instructions="Show exact routes and arguments before every Xquik request.", tools=approved_mcp, ) as agent: session = agent.create_session() run_options = {"response_format": TweetSearchHandoff} run_context = {"xquik_api_key": os.environ["XQUIK_API_KEY"]} result = await agent.run( "Search tweets about Python Twitter MCP agents.", session=session, options=run_options, function_invocation_kwargs=run_context, ) while result.user_input_requests: responses = [] for request in result.user_input_requests: if request.function_call is None: responses.append( request.to_function_approval_response(approved=False) ) continue print("Function:", request.function_call.name) print("Arguments:", request.function_call.arguments) answer = await asyncio.to_thread(input, "Approve this call? [y/N] ") responses.append( request.to_function_approval_response( approved=answer.strip().lower() == "y" ) ) result = await agent.run( Message(role="user", contents=responses), session=session, options=run_options, function_invocation_kwargs=run_context, ) ``` Show the route, method, arguments, text, and selected X account. Reject any request whose method, arguments, text, or account changed. For public research, a guest `paid_reads` key supplies a hard read-only boundary. It exposes only eligible GET routes. Review [guest wallet permissions](/guides/guest-wallets) before granting autonomous access. ## Scope Microsoft Agent Framework Tools for Discovery Expose only `explore` when an agent should inspect endpoint schemas. ```python theme={null} discovery_mcp = MCPStreamableHTTPTool( name="xquik-route-discovery", url="https://xquik.com/mcp", allowed_tools=["explore"], header_provider=lambda kwargs: { "x-api-key": kwargs["xquik_api_key"], }, ) ``` This configuration cannot execute Twitter API operations. Adding `xquik` enables every operation authorized by the key. Use `explore` before an unfamiliar operation. It returns routes, parameters, and response fields. Discovery never executes an X operation. ## Authenticate the Microsoft Agent Framework MCP Client Resolve one Xquik key for every run. Pass it through runtime context. ```python theme={null} def tenant_headers(kwargs: dict[str, object]) -> dict[str, str]: tenant_id = str(kwargs["tenant_id"]) return {"x-api-key": secret_store.get_xquik_key(tenant_id)} tenant_mcp = MCPStreamableHTTPTool( name="tenant-xquik-twitter-api", url="https://xquik.com/mcp", allowed_tools=["explore", "xquik"], header_provider=tenant_headers, ) async with Agent( client=OpenAIChatClient(model=os.environ["OPENAI_CHAT_MODEL"]), name="tenant_twitter_agent", instructions="Preserve exact profile IDs and usernames.", tools=tenant_mcp, ) as tenant_agent: result = await tenant_agent.run( "Look up the profile for microsoft.", function_invocation_kwargs={"tenant_id": authenticated_tenant_id}, ) ``` The `secret_store` represents your existing secret manager. Never include the returned key in prompts, sessions, handoffs, or logs. Microsoft Agent Framework 1.13.0 applies runtime headers during initialization and tool calls. It limits those headers to matching-origin requests. Create an isolated `httpx.AsyncClient` for each authenticated origin. ## Build Microsoft Multi-Agent Framework Workflows Teams evaluating a Microsoft multi-agent framework should isolate every agent's tools. Give only the researcher access to Twitter MCP. Pass its validated handoff to a tool-free reviewer. ```python theme={null} researcher = Agent( client=client, name="tweet_researcher", instructions="Return validated tweet rows and pagination state.", tools=xquik_mcp, ) reviewer = Agent( client=client, name="tweet_reviewer", instructions="Analyze only supplied tweets. Preserve every tweet_id.", ) research = await researcher.run( "Search latest tweets about Python agent frameworks.", options={"response_format": TweetSearchHandoff}, function_invocation_kwargs={"xquik_api_key": tenant_api_key}, ) if not isinstance(research.value, TweetSearchHandoff): raise RuntimeError("Tweet research invalid. Stop the handoff.") review = await reviewer.run(research.value.model_dump_json()) ``` The reviewer receives no MCP tools. These multi-agent workflows prevent unauthorized Twitter API execution. The reviewer cannot fetch unrelated tweets or post X actions. ## Handle Tweet Search Errors and Rate Limits The tweet search contract documents these responses. Handle each status independently. | Status | Meaning | Agent action | | ------ | --------------------------------------------- | ----------------------------------------------- | | `400` | The search query is missing or invalid | Fix `q`; do not retry unchanged | | `401` | Authentication cannot complete this request | Provide an Xquik key or connect X | | `402` | The account lacks credits | Stop and request account action | | `424` | The upstream X dependency failed | Retry with bounded backoff | | `429` | The Twitter API rate limit applies | Wait for reset guidance, then resume the cursor | | `502` | The X dependency returned an invalid response | Retry later without changing IDs | Do not restart pagination after `429`. Preserve `next_cursor` and completed tweet IDs. Deduplicate by `tweet_id` after recovery. POST and DELETE routes document different statuses. Derive retry behavior from each route's documented statuses. See [error handling](/guides/error-handling). ## Preserve Microsoft Agent Framework Workflows and Handoffs Store `tweet_id`, `text`, `author_username`, `created`, `url`, `has_more`, `next_cursor`, and the original `q`. Store source `id` as `user_id`, plus `username`, `name`, `followers`, `verified`, `profile_picture`, `has_more`, `next_cursor`, and the source lookup. Store `monitor_id`, `event_types`, `next_billing_at`, `webhook_id`, and `url`. Keep the one-time webhook `secret` in a secret manager. Store `event_id`, `type`, `monitor_id`, `occurred_at`, `has_more`, `next_cursor`, and the requested `cursor` value. Store `extraction_id`, `status`, `poll`, and `export_after_complete`. Poll before loading CSV, JSON, or XLSX rows. Store `tweet_id` or `write_action_id`, `reply_to_tweet_id`, `status`, `charged_credits`, and `poll`. Never resend a pending write. ## Choose MCP or the Direct REST API | Requirement | Microsoft Agent Framework with MCP | Direct REST API | | -------------------------------- | ---------------------------------- | ------------------------------ | | Natural-language tweet research | Strong fit | Write route selection yourself | | Microsoft agent sessions | Native fit | Add session orchestration | | Strict scheduled follower export | Extra model step | Strong fit | | Human-approved tweet posting | Function approval flow | Build approval state yourself | | Predictable latency and cost | Less predictable | More predictable | | Typed final handoff | Pydantic `response_format` | SDK or Pydantic model | Use MCP for real-time research across related Twitter API operations. Use REST for fixed routes, scheduled exports, or latency-sensitive services. ## Migrate Older Microsoft Agent Framework Code Use current 1.13 APIs. Several preview-era examples use removed names. Microsoft Agent Framework unifies lessons from Semantic Kernel and AutoGen. | Preview-era pattern | Current pattern | | -------------------------- | -------------------------------------------------- | | Preview agent class | `Agent(...)` | | Legacy client keyword | `client=` | | Legacy model identifier | Provider client's `model=` | | Dedicated streaming method | `run(..., stream=True)` | | Prompt-only JSON | Pydantic `response_format` and `result.value` | | Static shared auth headers | `header_provider` and `function_invocation_kwargs` | Current releases also scope runtime MCP headers to matching origins. Keep the framework updated for those authentication fixes. ## Verified Python Package Versions We checked these versions on August 3, 2026. | Package | Checked compatible version | Supported range used here | | ------------------------ | -------------------------- | ----------------------------- | | `agent-framework` | 1.13.0 | `==1.13.0` | | `agent-framework-core` | 1.13.0 | Installed by the meta-package | | `agent-framework-openai` | 1.12.0 | Installed by the meta-package | | `mcp` | 1.29.0 | `>=1.24,<2` | | `pydantic` | 2.13.4 | `>=2,<3` | Pin a tested release. Review Microsoft Agent Framework release notes before widening the range. ## Microsoft Agent Framework Twitter API Questions ### Does Microsoft Agent Framework Support MCP? Yes. Python agents connect through `MCPStreamableHTTPTool`. Xquik publishes `explore` and `xquik` at `https://xquik.com/mcp`. ### Is Microsoft Agent Framework an Alternative to MCP? No. Microsoft Agent Framework runs agents and workflows. MCP standardizes remote tool access. This guide uses both layers together. ### How Do I Connect Microsoft Agent Framework to a Twitter API? Create `MCPStreamableHTTPTool` with the Xquik MCP URL. Resolve the `x-api-key` header through `header_provider`. Pass the tool to `Agent.tools`. ### How Does Microsoft Agent Framework MCP Authentication Work? Xquik reads the `x-api-key` header. Resolve it through `header_provider` for each run. Pass its value through `function_invocation_kwargs`. ### Does the Xquik MCP Server Require OAuth? No. Xquik uses an API key. Do not add OAuth for this MCP server. Other MCP servers can require OAuth. ### Where Is the Microsoft Agent Framework Documentation? Microsoft Learn documents agents, MCP tools, sessions, approvals, and workflows. This guide applies those APIs to Xquik's Twitter operations. ### What Does the Microsoft Agent Framework SDK Provide? The SDK provides agents, chat clients, sessions, middleware, tools, and typed workflows. Xquik supplies remote Twitter operations through MCP. ### How Do I Search Tweets With a Python Agent? Ask the agent to call `GET /api/v1/x/tweets/search`. Supply an exact `q`, `queryType`, and limit. Preserve `tweet_id`, `created`, and `next_cursor`. ### Can Microsoft Agent Framework Post Tweets and Replies? Yes. Connect an X account first. Apply approval to every `xquik` request. Validate the selected account, text, reply ID, and media. See [create tweet](/api-reference/x-write/create-tweet) for the exact contract. ### How Do I Handle Twitter API Rate Limits in Python? Treat `429` separately from dependency errors. Save the cursor and completed tweet IDs. Resume after reset guidance. Never retry in a tight loop. ### Can a Microsoft Agent Export Twitter Followers? Yes. Use the [followers API](/api-reference/x/followers) for paginated profile rows. Use an extraction job for larger CSV, JSON, or XLSX exports. ### Can the Agent Monitor Tweets Without Repeated Searches? Yes. Create an account or keyword monitor. Replay stored events by cursor. Verify signatures and de-duplicate deliveries before connecting a webhook. ### Can the Agent Triage Customer Support Mentions? Yes. Search brand mentions or create an account monitor. Preserve tweet IDs before classifying urgency. Keep every reply behind human approval. ### Does Tool Filtering Make the Twitter API Read-Only? Only when `allowed_tools` contains `explore` without `xquik`. The `xquik` tool can run every operation authorized by its API key. ### Why Does My MCP Agent Lose Authentication Between Calls? Pass runtime values through `function_invocation_kwargs` on every resumed run. Keep one `AgentSession` during approvals. Never store the key in session state. # n8n Twitter Node for X API Automation & Webhooks Source: https://docs.xquik.com/guides/n8n Build n8n Twitter workflows for tweet search, follower exports, monitors, and webhooks. Add media posts, AI agents, and cursor-safe handoffs through Xquik.
For the complete documentation index, see llms.txt.
Build an n8n Twitter node workflow with Xquik. Search tweets, export followers, monitor accounts, publish media, and run AI agents. This n8n Twitter integration uses HTTP Request, Webhook, and MCP Client Tool nodes. Xquik does not require an X Developer app. Xquik uses one API key and the REST base URL `https://xquik.com/api/v1`. ## Choose an n8n Twitter Integration Use n8n's built-in X node for supported tweet, list, user, and DM operations. Choose Xquik's n8n Twitter API for wider reads, exports, monitors, webhooks, and MCP tools. ## Prerequisites * [Xquik API key](/x-api-quickstart) * n8n Cloud or self-hosted n8n * An HTTPS n8n webhook URL for monitor recipes * Optional Slack and Google Sheets credentials for the recipes below ## API Key Credential Create a reusable n8n credential for Xquik: Select Header Auth for the HTTP Request credential. Set the header name to `x-api-key`. Paste your Xquik API key as the credential value. Use that credential in every HTTP Request node that calls `https://xquik.com/api/v1`. ## Community Node Blueprint If you package Xquik as a community node, keep the first release narrow and reliable. Share reusable templates through the n8n community. Keep credentials outside exported workflow JSON. Use custom code nodes only for signature checks or bounded transforms. Create an `Xquik API Key` credential and inject it as `x-api-key`. Point every REST action at `https://xquik.com/api/v1`. Support `GET`, `POST`, `PATCH`, `DELETE`, JSON bodies, structured errors, and `Retry-After` backoff. Start with Tweet, User, Trends, Extraction, Monitor, and Webhook resources. Ship Get Tweet, Search Tweets, Get User, Get Trends, Create Tweet, Create Extraction, Create Monitor, and Create Webhook first. Add Monitor Event Webhook and Extraction Completed Polling sources. Handle these response classes explicitly: Surface the Xquik `error` and `message` fields in the failed item. Ask the user to check the Header Auth credential and `x-api-key` value. Route to subscription or credit setup before retrying the node. Read `Retry-After` from response headers and wait before retrying. Enable n8n Retry on Fail with exponential backoff, then fail the item. ## Result Handoff Use an Edit Fields node after each HTTP Request node when the next node only needs stable handoff fields. Keep raw responses out of Slack messages, Sheets rows, and retry queues. Use snake\_case storage keys for handoff rows even when direct API responses use camelCase. Store request `q`; map each tweet `id`, `text`, `author.username`, and `createdAt` to `tweet_id`, `text`, `author_username`, and `created_at`; keep `has_next_page` and `next_cursor` for page loops. Store source `id` as `user_id`, plus `username`, `name`, `followers`, `verified`, `profile_picture`, `has_next_page`, `next_cursor`, and the lookup or search input. Store each trend `name`, `rank`, `query`, and `description`. Keep response `count`, `woeid`, and the requested region with the workflow run. Send a unique `Idempotency-Key`. Store `id`, `status`, `billing`, `result`, and `statusUrl`. Poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. For tweets or replies, pass public URLs in `media`; do not send `media_ids`. For DMs, upload first, pass 1 `media_id` in `media_ids`, store `message_id`, and leave `reply_to_message_id` unset. Store monitor `id`, `username`, `xUserId`, `eventTypes`, `isActive`, and `nextBillingAt`; store webhook `id`, `url`, `eventTypes`, and one-time `secret`. For storage rows, map production `deliveryId` to `delivery_id` for receiver retry de-dupe and `streamEventId` to `stream_event_id` when one monitor event should process once across endpoint changes. Store `deliveryId` for receiver retry de-dupe and `streamEventId` when one monitor event should process once across endpoint changes. Call `GET /events` with `cursor` when a workflow needs replay. Map `id`, `monitorId`, `monitorType`, `occurredAt`, `hasMore`, and `nextCursor` to `event_id`, `monitor_id`, `monitor_type`, `occurred_at`, `has_more`, and `next_cursor`. Return `2xx` after accepting duplicate `deliveryId` or `streamEventId`; keep endpoint signing values, raw request body, raw signature, and full headers out of n8n executions, data stores, Slack messages, Sheets rows, and retry queues. Store `id` as `extraction_id`, `toolType` as `tool_type`, plus `status`, `has_more`, and `next_cursor` before batch loops fetch detail rows. ## Recipe 1: AI Agent X Research With MCP Use this recipe to integrate AI agents with Xquik. The agent can search live tweets, summarize trends, inspect profiles, and compare follower activity. Create an AI Agent workflow with your preferred chat model. Set Server Transport to `HTTP Streamable`. Set MCP Endpoint URL to `https://xquik.com/mcp`. Set Authentication to `Header Auth`. Create or select a Header Auth credential. Set Name to `x-api-key` and Value to your Xquik API key. Tell the agent to use Xquik for X search, trends, account lookups, and extraction planning. The MCP Client Tool also supports `MCP OAuth2`. This recipe uses Header Auth for unattended workflows with an n8n credential. Suggested agent system prompt: ```text theme={null} Use Xquik for every X research request. Search tweets, inspect accounts, fetch trends, and cite the returned post URLs. Prefer small, targeted searches before broad extraction jobs. ``` Example user prompt: ```text theme={null} Find recent posts about AI agents in fintech, group them by theme, and list 5 accounts worth monitoring. ``` The MCP server exposes `explore` for endpoint discovery. It exposes `xquik` for authenticated calls. One agent loop can select an endpoint and request current API data. ## Recipe 2: Monitor To Slack Use this when a Slack channel should receive new tweets, replies, quotes, or retweets from monitored accounts. ### Workflow Shape Receive Xquik monitor events on the production Webhook URL. Use HTTP Request to call `POST /webhooks` with the Webhook Trigger URL. Use HTTP Request to call `POST /monitors` for the username and event types. Use Set to build Slack text from `data`, with `username` as fallback. Send the formatted message to Slack after the webhook and monitor exist. ### Create Webhook Call this once during setup, using the production URL from your n8n Webhook Trigger. ```json theme={null} { "method": "POST", "url": "https://xquik.com/api/v1/webhooks", "headers": { "x-api-key": "={{$credentials.xquikApi.apiKey}}" }, "body": { "url": "={{$node['Webhook Trigger'].webhookUrl}}", "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote", "tweet.retweet"] } } ``` ### Create Monitor ```json theme={null} { "method": "POST", "url": "https://xquik.com/api/v1/monitors", "headers": { "x-api-key": "={{$credentials.xquikApi.apiKey}}" }, "body": { "username": "username", "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote", "tweet.retweet"] } } ``` ### Slack Message Use a Set node before Slack: ```text theme={null} {{$json.eventType}} from @{{$json.data.author?.userName || $json.username}} {{$json.data.text}} https://x.com/{{$json.data.author?.userName || $json.username}}/status/{{$json.data.id}} ``` Webhook tweet fields live under `data`. Use `data.author.userName` when present; `username` is the monitored-account fallback. Keep the webhook secret returned by Xquik. Verify `x-xquik-signature` before sending Slack messages. Use custom verification code when your plan supports it. ## Recipe 3: Extraction To Google Sheets Use this when a team needs bulk follower, reply, quote, media, list, community, or search results in a spreadsheet. ### Workflow Shape Run daily or hourly exports; execute manually while testing. Use HTTP Request to call `POST /extractions` and store the returned `id`. Pause before the first status check so the extraction can start. Use HTTP Request to call `GET /extractions/{id}` and read `job.status`. Use IF to continue only when `job.status` is `completed`. Use Google Sheets Append Row with the mapped result fields below. ### Create Extraction ```json theme={null} { "method": "POST", "url": "https://xquik.com/api/v1/extractions", "headers": { "x-api-key": "={{$credentials.xquikApi.apiKey}}" }, "body": { "toolType": "tweet_search_extractor", "searchQuery": "AI agents lang:en", "resultsLimit": 500 } } ``` Store the returned `id` for the polling step. ### Poll Results ```json theme={null} { "method": "GET", "url": "https://xquik.com/api/v1/extractions/{{$json.id}}", "headers": { "x-api-key": "={{$credentials.xquikApi.apiKey}}" }, "query": { "limit": "1000" } } ``` Map these fields into Google Sheets: Map to `id`. Map to `author.username`. Map to `text`. Map to `createdAt`. Map to `likeCount`. Map to `retweetCount`. Build from `https://x.com/{author.username}/status/{id}`. For jobs larger than 1,000 rows, loop while `hasMore` is `true` and pass `cursor` with the `nextCursor` value. ## Useful REST Actions Read one or more posts with `GET /x/tweets?ids=`. Run X query searches with `GET /x/tweets/search?q=`. Fetch an X profile with `GET /x/users/{id}`. Read regional X trends with `GET /x/trends`. Publish text or media posts with `POST /x/tweets`. Start bulk export jobs with `POST /extractions`. Watch accounts or keywords with `POST /monitors`. Register signed delivery URLs with `POST /webhooks`. ## Testing Checklist * Use the n8n Test Step action on each HTTP Request node. * Confirm every request includes `x-api-key`. * Confirm monitor webhooks use the production webhook URL, not the test URL. * Send a test event from [Test Webhook](/api-reference/webhooks/test). * For extraction jobs, poll until `completed` before writing to Sheets. * On `429`, wait for the `Retry-After` header before retrying. ## n8n Twitter Automation Questions ### How Do I Connect n8n to Twitter? Create Header Auth with `x-api-key`. Select it on every Xquik request. ### How Does n8n Twitter Search Work? Call the tweet-search endpoint. Store the query and cursor for every page. ### Can n8n Post to Twitter? Treat each n8n Twitter post as approved. Send `Idempotency-Key`, then poll the returned action. ### Can n8n Scrape Twitter? An n8n Twitter scraper should call documented endpoints. Never make n8n scrape Twitter pages. API responses preserve stable IDs and cursors. ### How Does n8n Twitter Media Upload Work? Tweet and reply writes accept public URLs. DMs require one uploaded media ID. ### How Do I Monitor Twitter Mentions in n8n? Build an n8n Twitter monitoring workflow with a monitor and production webhook. Verify every signature before routing tweets. ### Can I Build an n8n Twitter Bot or n8n Twitter Agent? An n8n Twitter bot needs approval before writes. An n8n Twitter agent can search tweets, profiles, trends, and followers through MCP. ### Can Self-Hosted n8n Use Xquik? Yes. Allow outbound HTTPS. Monitor recipes also need a public HTTPS webhook. ### How Do I Troubleshoot an n8n Twitter Connection? Check the base URL, Header Auth, and `x-api-key`. Use the production webhook. Reselect edited credentials. Recreate them if access still fails. ### Which n8n Twitter Template Should I Build First? Start with tweet search and cursor storage. Add exports or monitors next. ## Next Steps * Read [Webhooks](/webhooks/overview) for payload and retry behavior. * Read [Extraction Workflow](/guides/extraction-workflow) for pagination and export patterns. * Use [MCP Tools](/mcp/tools) when building AI Agent workflows. # No-Code Twitter Automation with Webhooks & Exports Source: https://docs.xquik.com/guides/no-code-workflow-handoff Connect Xquik monitor webhooks, extraction jobs, tweet search pages, and follower exports to Zapier, Make, Pipedream, n8n, Sheets, CRM, and queue workflows.
For the complete documentation index, see llms.txt.
Build no-code Twitter automation for tweets, replies, followers, profiles, and team alerts. Use monitor webhooks for fresh events. Use direct reads for small pages. Use extraction jobs for large exports. This no code API integration pattern preserves cursors and event IDs. Choose Twitter automation tools by freshness, row count, and approval needs. API integration automation can save time when every API call commits its checkpoint. A Twitter webhook workflow should verify each event before routing. Use an automated tool only for bounded reads or approved writes. Keep REST API calls idempotent around retries. Store secrets outside workflow history. ## Choose a Twitter Automation Outcome | Outcome | Best lane | Required checkpoint | | ------------------------------------- | ----------------------- | ----------------------- | | Alert on new tweets or replies | Account monitor webhook | `streamEventId` | | Track keyword or brand matches | Keyword monitor webhook | `streamEventId` | | Add tweet search rows to Sheets | Direct read pages | `next_cursor` | | Export followers or following to CRM | Extraction job | Job ID and `nextCursor` | | Repair events after receiver downtime | Stored event replay | `nextCursor` | | Audit failed webhook deliveries | Delivery log | `deliveryId` | Choose the lane from the required freshness and row count. Do not poll a webhook workflow. Do not build a bulk export from repeated page-one reads. ## Pick the Handoff Lane Use `POST /api/v1/monitors` or `POST /api/v1/monitors/keywords`, then `POST /api/v1/webhooks`, when the workflow needs fresh account or keyword events. Use `POST /api/v1/extractions`, poll `GET /api/v1/extractions`, then export CSV, JSON, or XLSX when the workflow needs many rows. Use `GET /api/v1/x/tweets/search` or follower pages when the workflow owns the cursor loop and can store `next_cursor`. Use `GET /api/v1/events` and `GET /api/v1/webhooks/{id}/deliveries` after receiver downtime, failed steps, or queue backpressure. ### Webhook or Polling? Use webhooks for fresh events. Poll only durable extraction jobs. Use direct reads for bounded operator searches. Store each cursor before the next page. Never use repeated polling as a substitute for monitor webhooks. It creates duplicate tweet reads and weaker recovery checkpoints. ## Monitor Event Trigger Choose the monitor first. Point its webhook at the no-code receiver. Test the webhook before routing alerts to Slack, Sheets, CRM, or queues. ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/xquik/no-code-receiver", "eventTypes": ["tweet.new", "tweet.reply"] }' | jq ``` ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks/15/test \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Build the receiver in this order: 1. Capture the raw request body and signature headers. 2. Verify the timestamp, nonce, HMAC, and 5-minute tolerance. 3. Reject repeated nonces within that same window. 4. Store `deliveryId` and `streamEventId` before routing. 5. Queue the normalized event, then return `2xx`. Xquik signs `..` with HMAC-SHA256. Read `X-Xquik-Timestamp`, `X-Xquik-Nonce`, and `X-Xquik-Signature` first. Follow [Webhook Signature Verification](/webhooks/verification). Some visual webhook triggers parse JSON before exposing request bytes. Route those triggers through a small verified receiver. Forward only verified fields into the visual workflow. Use `deliveryId` for endpoint retries. Use `streamEventId` across receivers. ### Map the Monitor Payload Production monitor events include these stable fields: ```json theme={null} { "eventType": "tweet.new", "schemaVersion": 1, "deliveryId": "502", "streamEventId": "9002", "occurredAt": "2026-05-24T20:33:00.000Z", "username": "customer_handle", "data": { "id": "1893704267862470862", "text": "A matched public tweet", "author": { "id": "987654321", "userName": "customer_handle" } } } ``` Branch on `eventType`. Store `data.id`, `data.text`, and `data.author.id`. Keyword events can include `query`. Account events can include `username`. Test deliveries stop before CRM or alert steps. ## Bulk Export Trigger Use extraction jobs for files, large batches, or repeatable job receipts. ```bash theme={null} curl "https://xquik.com/api/v1/extractions?status=completed&limit=25" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```bash theme={null} curl "https://xquik.com/api/v1/extractions/77777/export?format=csv" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -o xquik-export.csv ``` Store `job.id`, `job.toolType`, `job.status`, `hasMore`, and `nextCursor` first. Use this extraction sequence: 1. Create the extraction and store its returned `id`. 2. Poll that job until `job.status` becomes `completed`. 3. Route terminal failures to an operator. 4. Page results or download CSV after completion. 5. Store `nextCursor` after each destination write. Read job status before exporting. Resume failures from their committed cursor. Use [Twitter API Export Formats](/guides/response-formats-exports) for CSV, JSON, XLSX, Markdown, PDF, TXT, and pagination limits. ## Direct Read Loop Use direct reads for small pages with persistent cursor storage. ```bash theme={null} curl "https://xquik.com/api/v1/x/tweets/search?q=xquik%20min_faves%3A10&limit=50" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```bash theme={null} curl "https://xquik.com/api/v1/x/users/username/followers?pageSize=200" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Store `has_next_page` and `next_cursor`. Pass `next_cursor` as `cursor` only when another page exists. Stop on repeated cursors or missing IDs. Commit rows and `next_cursor` together. Upsert on tweet ID or X user ID. ### Choose Direct Reads by Task Search keywords, hashtags, authors, dates, and engagement filters. Store each tweet ID before requesting another page. Export follower IDs, usernames, names, bios, and public metrics. Upsert on the stable X user ID. Export accounts followed by a public profile. Keep source profile and collection time beside each relationship. Store reply tweet IDs, parent tweet IDs, author IDs, and timestamps. Keep the endpoint cursor with the same filters. ## Shared Checkpoint Store one stable checkpoint before alerts, CRM, spreadsheets, or classification. ```json theme={null} { "workflow_id": "xquik-no-code-q2", "handoff_lane": "instant_monitor", "delivery_id": "502", "stream_event_id": "9002", "event_type": "tweet.new", "tweet_id": "1893704267862470862", "user_id": "987654321", "retry_key": "delivery_id:502", "event_dedupe_key": "stream_event_id:9002", "replay_route": "GET /api/v1/events?monitorId=42&cursor={nextCursor}", "received_at": "2026-05-24T20:33:00.000Z" } ``` Store IDs as strings. Keep secrets, raw bodies, signatures, and headers out of workflow history. ### Persist the Workflow Checkpoint Update rows and their cursor in one transaction or idempotent platform step. ```json theme={null} { "workflow_id": "xquik-no-code-q2", "monitor": { "last_stream_event_id": "9002", "next_event_cursor": "9003" }, "direct_read": { "route": "/api/v1/x/tweets/search", "query": "xquik min_faves:10", "next_cursor": "DAABCgAB" }, "extraction": { "job_id": "77777", "status": "completed", "next_cursor": "1001" }, "updated_at": "2026-05-24T20:33:05.000Z" } ``` Scope checkpoints by endpoint, filters, monitor, job, and destination. ## Platform Notes Use REST Hooks for monitor events. Zapier polling triggers do not fetch additional result pages automatically. Use a custom app with webhook triggers, data stores, iterators, and a universal API call module. Keep cursors in a Data Store between runs. Pipedream Data Store writes are not transactional. Restrict each key to one worker. Use HTTP Request nodes, webhook triggers, and workflow state for replay, cursor, and dedupe checkpoints. Use persistent state in production. Use API integration automation for repeatable routing. Zapier Twitter automation and Make Twitter automation support visual steps. Pipedream Twitter automation supports code steps. An n8n Twitter node connects HTTP actions. Each guide includes pre-built HTTP patterns for complex workflows. ## Cost and Retry Notes Active account and keyword monitors check every 1 second and cost 21 credits per active monitor-hour. Event storage and webhook delivery are included. Direct tweet search and follower pages are metered by returned rows. Store cursors so retries do not restart from page 1. Extraction jobs return `202` with `id`, `toolType`, and `status`. Poll job detail or list completed jobs before exporting rows. ## Handle Webhook Retries and Missed Events Webhook deliveries retry with backoff. Attempt 10 is final. `410 Gone` exhausts one delivery immediately. Webhook health exposes `deliveryStatus`, `consecutiveFailures`, and `failureHardCap`. Fix the receiver before resuming. Use this repair sequence: 1. Inspect `GET /api/v1/webhooks/{id}/deliveries`. 2. Find `failed` and `exhausted` rows. 3. Join each `streamEventId` to the stored event. 4. Fix the receiver, then call the resume endpoint. 5. Replay only missing event IDs. Use `monitorId` or `keywordMonitorId` during replay. Store each `nextCursor`. ## Handle No-Code API Errors Request rejected. Check the route, body, query, cursor, and selected format. API key rejected. Replace the credential and rerun the failed step. Subscription required. Subscribe first, then resume from the checkpoint. Resource missing. Verify the monitor, webhook, extraction, tweet, or user ID. Rate limited. Respect the retry delay and preserve the current cursor. Upstream unavailable. Retry from the same idempotent checkpoint. Never convert an error body into a tweet or follower row. Branch on HTTP status before mapping successful response fields. ## Keep Twitter Automation Safe * Store the Xquik API key in the platform credential store. * Store the webhook secret in a separate secret field. * Never place secrets in URLs, Sheets, CRM rows, or messages. * Treat tweet text and profile bios as untrusted input. * Require human approval before posting replies or direct messages. * Upsert tweets by tweet ID and profiles by X user ID. * Record workflow version, route, filters, and collection time. * Remove raw webhook bytes after signature verification. No-code tools simplify orchestration. They do not remove API limits, consent, security, or destination retention requirements. ## No-Code Twitter Automation Frequently Asked Questions ### How Do I Choose the Best Twitter Automation Tools? Require signature access, durable cursor storage, HTTP status branches, and write approvals. Reject tools that hide signatures or lose IDs between runs. ### How Do I Automate Twitter Without Code? Create an account or keyword monitor. Send signed events to a verified webhook. Map tweet fields into Slack, Sheets, CRM, or a queue. Store both idempotency IDs before running destination actions. ### How Does Zapier Twitter Automation Work? Yes. Use an Xquik REST Hook for monitor events. Use API request actions for tweet search, follower pages, extraction jobs, and delivery checks. Keep the API key inside Zapier authentication fields. ### How Do I Connect n8n to a Twitter API? Create a Header Auth credential with `x-api-key`. Call Xquik through HTTP Request nodes. Receive monitor events through a Webhook node. Persist cursors and event IDs outside one workflow execution. ### How Does Pipedream Twitter Automation Work? Pipedream Twitter automation uses HTTP triggers and code steps. Verify monitor signatures before mapping tweets into downstream actions. ### Should Twitter Automation Use Webhooks or Polling? Use webhooks for fresh tweet, reply, quote, repost, or keyword events. Poll durable extraction jobs. Use direct reads for operator-triggered page requests. ### How Do I Stop Duplicate Twitter Alerts? Verify the signature first. Store `deliveryId` for delivery retries. Store `streamEventId` when one monitor event should run once across webhook changes. ### How Do I Recover Missed Twitter Events? Inspect failed deliveries. Page stored events with the correct monitor filter. Join failed `streamEventId` values to event IDs. Reprocess only missing IDs. ### Can I Export Twitter Followers to Sheets or CRM? Yes. Use follower pages for bounded workflows. Use extraction jobs for large exports. Store X user IDs as strings and use them as upsert keys. ### Can a Workflow Export Accounts I Follow? Yes. Use the following endpoint or a matching extraction job. Store the source profile, followed X user ID, collection time, and pagination cursor. ### Can No-Code Twitter Automation Post Replies? Use the documented write endpoint through the platform's HTTP module. Add an approval step before the request. Store the returned write action ID. ### How Do Twitter Automated Posts Work Without Code? Use a schedule trigger to prepare the post. Treat automated Twitter posts as approved write jobs. Call the create-tweet API endpoint only after approval. Store the returned action ID and final tweet ID. Never resend a pending write. ### How Can I Schedule Tweets Without Coding? Use the no-code platform's scheduler to schedule tweets. Keep Xquik focused on the approved write request. Store the selected Twitter account and publish time. ### Can Twitter Automation Reply to Keywords? Create a keyword monitor first. Send matching tweets into an approval queue. Publish a reply only after a person reviews the text and target tweet. ### How Do I Automate Twitter Hashtag Research? Search the hashtag with date, language, and engagement filters. Store tweet IDs in Google Sheets or CRM. Review the research before posting anything. ### Are Twitter Automation Tools Safe? They are safe when credentials, approvals, and limits stay explicit. Keep API keys in the platform vault. Never turn untrusted tweet text into instructions. ### Should I Send Automated DMs to New Followers? No. Avoid unsolicited direct messages. Use the DM endpoint only for approved, expected conversations. Store the message ID after a successful send. ### Does No-Code Automation Bypass Twitter API Limits? No. Every read, monitor, extraction, and write follows its documented contract. Use cursors, batching, and idempotency to avoid wasteful duplicate requests. ## Next Steps Build account and keyword monitor workflows with signed webhooks and replay. Create extraction jobs, fetch paginated results, and export files. Verify signed test deliveries before accepting production events. Choose CSV, JSON, XLSX, PDF, or paginated JSON for downstream tools. # Xquik Open Source Docs, MIT License & OpenSSF Source: https://docs.xquik.com/guides/open-source-assurance Audit Xquik's open source API documentation, MIT license, REUSE metadata, OpenSSF evidence, security reporting, CI checks, and safe contribution steps.
For the complete documentation index, see llms.txt.
Xquik publishes its API documentation source under the MIT License. You can inspect, test, fork, and improve these docs. This license does not make the hosted Xquik platform open source. It does not grant rights to Xquik brands either. Use this page to verify the exact boundary. It covers documentation, OpenAPI, SDKs, security reporting, dependencies, and OpenSSF evidence. ## Distinguish the Public Sources The [xquik-docs repository](https://github.com/Xquik-dev/xquik-docs) uses the MIT License. It contains MDX pages, `docs.json`, tests, and build policy. The public [`openapi.yaml`](https://github.com/Xquik-dev/xquik-docs/blob/main/openapi.yaml) describes REST paths, parameters, schemas, and responses. Published SDKs use separate repositories. Start from the [SDK documentation](/sdks) or browse the [Xquik-dev organization](https://github.com/Xquik-dev). The API, dashboard, workers, and private platform code are not licensed by this repository. Public documentation and a public API contract do not expose a hosted service's implementation. They let developers audit the promised interface instead. ## Inspect the Open Source API Documentation Review [`LICENSE`](https://github.com/Xquik-dev/xquik-docs/blob/main/LICENSE). It grants the standard MIT permissions for this repository's source. Review [`REUSE.toml`](https://github.com/Xquik-dev/xquik-docs/blob/main/REUSE.toml) and [`LICENSES/MIT.txt`](https://github.com/Xquik-dev/xquik-docs/blob/main/LICENSES/MIT.txt). REUSE metadata assigns an SPDX license and copyright statement to committed files. Compare endpoint pages with [`openapi.yaml`](https://github.com/Xquik-dev/xquik-docs/blob/main/openapi.yaml). Confirm methods, parameters, request bodies, and every response status. Inspect commits and pull requests. Read discussions, checks, approvals, and resolved review threads before trusting a change. Clone the repository. Install the lockfile without lifecycle scripts. Run the documented verification commands. ```bash theme={null} git clone https://github.com/Xquik-dev/xquik-docs.git cd xquik-docs npm ci --ignore-scripts npm run check:dependencies npm run check:response-examples npm run test:agent-docs npm run docs:validate npm run docs:links npm audit --audit-level=low reuse lint ``` Run commands against a reviewed commit. A passing local build does not prove the hosted platform uses that commit. ## Understand What Each Check Proves `check:dependencies` requires exact direct versions. It checks approved registry URLs, SHA-512 integrity, and allowed package licenses. `check:response-examples` compares every API response widget with the canonical OpenAPI status set. `test:agent-docs` checks metadata, navigation, contracts, accessibility, agent readability, and protected content invariants. `docs:validate` validates the OpenAPI document and Mintlify build. `docs:links` rejects broken internal links. `reuse lint` verifies SPDX coverage. The repository also stores the complete MIT license text. GitHub Actions runs dependency checks, CodeQL, OpenSSF Scorecard, audits, validation, and license verification. The workflows pin third-party actions to commit hashes. Checkout disables persisted credentials. The main docs job uses read-only repository access. These controls provide reproducible evidence. They cannot guarantee zero vulnerabilities. They also do not disclose private platform code. ## Understand the OpenSSF Badge Scope OpenSSF assigns badges to FLOSS projects. A shared project site does not automatically need a separate badge. See the official [project terminology](https://www.bestpractices.dev/en/criteria_discussion#terminology). `xquik-docs` supports multiple independently released projects. It has no separate badge entry today. Create one if this repository independently releases software. The [organization evidence register](https://github.com/Xquik-dev/.github/blob/main/OPENSSF.md) maps standalone projects to live bestpractices.dev entries. Open each entry for its current status and evidence. Do not copy dated percentages into a permanent claim. Passing does not mean Silver or Gold. Each level adds criteria. Read the [current Gold criteria](https://www.bestpractices.dev/en/criteria/2) before evaluating a project. ## Review Current OpenSSF Gaps The public evidence register identifies human requirements separately from automated checks. Current tracked areas include: * Maintainer and release continuity after one member becomes unavailable * A bus factor supported by public role and contribution evidence * Significant work from unassociated human contributors * A scoped human security review for each affected project Use the public trackers for current evidence: * [Human Silver and Gold prerequisites](https://github.com/Xquik-dev/.github/issues/3) * [Human security review scope](https://github.com/Xquik-dev/.github/issues/5) * [Maintainer nomination and continuity](https://github.com/Xquik-dev/.github/issues/8) Automated scans can support a human review. They cannot replace the reviewer. Open pull requests also cannot prove a default-branch control. Do not claim Gold until each project has verified public evidence. Recheck badge entries after evidence reaches their default branches. ## Apply the MIT License Correctly The MIT License lets you use, copy, modify, merge, and publish this documentation source. It also permits distribution. Preserve its copyright and permission notice. The repository-wide REUSE annotation applies MIT metadata to committed files. Third-party packages still retain their own licenses. Review the dependency policy before redistributing a complete development environment. The docs license does not cover the Xquik product, brand, or hosted platform. It also does not promise self-hosting instructions for the service. Hosted API access follows each route's authentication, payment, and account requirements. Start with the [X API quickstart](/x-api-quickstart) for authenticated requests. ## Contribute to the API Documentation Fix a contract error, example, broken link, accessibility issue, or unclear workflow. Avoid search-only pages and unsupported claims. Read [`CONTRIBUTING.md`](https://github.com/Xquik-dev/xquik-docs/blob/main/CONTRIBUTING.md). Match its writing, OpenAPI, dependency, and MDX rules. Protect corrected contracts, metadata, navigation, or content with the nearest test. Run the complete command block above. Fix failures before requesting review. Add the Developer Certificate of Origin sign-off. Open one focused pull request against `main`. Address every applicable comment. Wait for required checks and an independent approval. Use `git commit --signoff` for the DCO trailer. The shared [review policy](https://github.com/Xquik-dev/.github/blob/main/REVIEWING.md) defines approval expectations. ## Report Documentation Security Issues Privately Use [GitHub private vulnerability reporting](https://github.com/Xquik-dev/xquik-docs/security/advisories/new) for security findings. Email [security@xquik.com](mailto:security@xquik.com) if GitHub is unavailable. Do not open a public issue for authentication, webhook, contract, or credential vulnerabilities. Remove API keys, tokens, cookies, and personal information from every sample. The docs security scope covers this Mintlify site and dangerous contract errors. Product vulnerabilities follow the routing in the [security reporting guide](/security). ## Open Source API Documentation Questions ### Is the Xquik API Open Source? The documentation, OpenAPI contract, and listed SDK repositories are public. The hosted Xquik platform is not open source. ### Can I Self-Host Xquik From This Repository? No. This repository builds documentation. It does not contain the hosted API, dashboard, workers, or private platform code. ### Can I Fork the Xquik Documentation? Yes. Follow the MIT License and preserve its notice. Do not imply affiliation or endorsement. ### Is the Public OpenAPI File the Server Source? No. `openapi.yaml` defines the supported public interface. It does not contain server implementation code. ### Does xquik-docs Have an OpenSSF Badge? No separate entry exists. The repository is a shared project site for independently released projects. ### Where Can I Verify Current OpenSSF Status? Use the organization evidence register. Then open each linked bestpractices.dev entry for live status. ### How Do I Report a Vulnerability? Use private vulnerability reporting. Never publish secrets or exploitable details in an issue or pull request. ## Evidence Sources * [xquik-docs repository](https://github.com/Xquik-dev/xquik-docs) * [MIT License](https://github.com/Xquik-dev/xquik-docs/blob/main/LICENSE) * [OpenSSF evidence register](https://github.com/Xquik-dev/.github/blob/main/OPENSSF.md) * [OpenSSF project terminology](https://www.bestpractices.dev/en/criteria_discussion#terminology) * [Contribution guide](https://github.com/Xquik-dev/xquik-docs/blob/main/CONTRIBUTING.md) * [Security policy](https://github.com/Xquik-dev/xquik-docs/blob/main/SECURITY.md) * [Shared review policy](https://github.com/Xquik-dev/.github/blob/main/REVIEWING.md) Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. # Pipedream Automation for Twitter API & Webhooks Source: https://docs.xquik.com/guides/pipedream Build Pipedream workflow automation for tweet search, follower exports, Twitter monitors, signed webhooks, scheduled reads, and approved posts through Xquik.
For the complete documentation index, see llms.txt.
Build a Pipedream Twitter integration for searches, profiles, monitors, and approved posts. Xquik supplies one REST API and one API key for every workflow. The initial package contains one app file, eight actions, and two sources. Add another action after workflows repeatedly need the same HTTP request. ## Choose a Pipedream Automation Pattern Pipedream Workflows combine one trigger with actions or code steps. An HTTP trigger receives signed Xquik monitor events. A schedule trigger starts tweet search automation or extraction polling. An RSS trigger can start an approved publishing queue. Choose a private component for actions shared by multiple team members. Choose a custom JavaScript step for one custom API integration. The workflow uses prebuilt actions for Slack, Google Sheets, and CRM handoffs. Keep one output per workflow: tweet alerts, profile rows, or approved posts. Pipedream workflow automation becomes fragile when unrelated jobs share one trigger. Split searches, monitor events, follower exports, and approved writes. These boundaries turn complex workflows into small, testable paths. They also make error handling and rate limits easier to inspect. This Pipedream automation platform can replace manual tasks like copying tweets. It should never hide approval for tweets, replies, or direct messages. Review every write payload before the workflow sends it. This guide calls Xquik routes directly. It does not require Pipedream's native Twitter integration. ## Build a Serverless Twitter API Integration This serverless API integration runs without a dedicated workflow server. Start every Xquik request at `https://xquik.com/api/v1`. Send the API key through the `x-api-key` header. Pipedream secret props should contain all API keys. Step exports, logs, and error messages must exclude these keys. `GET /x/tweets/search` supports each scheduled Twitter automation workflow. Preserve the query, filters, limit, and opaque cursor between pages. Destinations should store tweet IDs after accepting each row. This order prevents skipped tweets after partial failures. `GET /x/users/{id}` returns the profile fields required for enrichment. Export usernames, follower counts, verification state, and profile images. Use `POST /extractions` when exports require multiple API pages. Poll the returned job until it reaches a terminal status. Custom JavaScript automation can read `steps.trigger.event` after a trigger. HTTP events include method, body, headers, path, query, and URL fields. Later steps receive tweet IDs, text, usernames, cursors, and destination keys. Never export signing secrets, raw signatures, or complete request headers. A shared request helper keeps API integrations consistent. It should normalize statuses, summaries, cursors, and retry instructions. Name every step after its concrete output. Examples include `search_tweets`, `normalize_profiles`, and `send_slack_alert`. ## Prerequisites * [Xquik API key](/x-api-quickstart) * Pipedream account * Node.js supported by the current Pipedream CLI * Pipedream CLI installed and signed in ```bash theme={null} npm install -g @pipedream/cli pd login ``` ## Component Shape `components/xquik/app/xquik.app.ts` API key prop injected as `x-api-key`. `https://xquik.com/api/v1` Get Tweet, Search Tweets, Get User, Get Trends, Create Tweet, Create Extraction, Create Monitor, and Create Webhook. Monitor Event Webhook and Extraction Completed Polling. JSON requests, structured Xquik errors, and `Retry-After` handling. ## App File Create the shared app component first: ```typescript theme={null} import { axios } from "@pipedream/platform"; export default { type: "app", app: "xquik", propDefinitions: { apiKey: { type: "string", label: "Xquik API Key", secret: true, }, }, methods: { async request($, config, apiKey) { return axios($, { ...config, baseURL: "https://xquik.com/api/v1", headers: { "content-type": "application/json", "x-api-key": apiKey, ...(config.headers || {}), }, }); }, }, }; ``` Add `apiKey: { propDefinition: [xquik, "apiKey"] }` to each action and source that calls `xquik.request`. Use `GET /account` as the first authentication check. It verifies the API key without changing an X account. ## Shared Error Handling Wrap requests so every action and source reports the same remediation: ```typescript theme={null} function xquikErrorMessage(status: number, body: unknown, headers: Record) { if (status === 401) return "Authentication failed. Check the Xquik API key."; if (status === 402) return "Subscription or credits required. Update billing in Xquik."; if (status === 429) { const retryAfter = headers["retry-after"]; return retryAfter ? `Rate limited. Retry after ${retryAfter} seconds.` : "Rate limited. Retry after the cooldown period."; } if (typeof body === "object" && body !== null && "message" in body) { return String((body as { message: unknown }).message); } return "Xquik request failed."; } ``` Call the helper once per request. Export a short `$summary` so each workflow run stays scannable. ## Control Errors, Retries, and Rate Limits Route each documented Xquik status before a Pipedream step exports anything. A `400` response means the request needs different fields. A `401` response means the API key failed authentication. A `402` response requires a subscription or credit change. A `404` response identifies a missing tweet, profile, monitor, or job. A `424` response reports an upstream dependency failure. A `429` response means the workflow exceeded a rate limit. A `502` response reports a temporary retrieval failure. Read `Retry-After` when the response includes it. Automatic retries should exclude `400`, `401`, `402`, and `404`. Safe reads can retry `424`, `429`, or `502` with bounded backoff. Write retries must follow the returned `safeToRetry` field. Send a new idempotency key only when the contract permits another attempt. Use Pipedream concurrency controls for fixed workflow execution limits. Concurrency limits do not replace endpoint rate limits. Place search and profile actions behind one shared throttle policy. Place tweet and reply writes behind a separate approval queue. Persist a page cursor only after downstream processing succeeds. The workflow can rerun a failed page and upsert by tweet or profile ID. This strategy protects Slack alerts, CRM rows, and warehouse loads. ## Starter Actions Call `GET /x/tweets/{id}` and return one tweet. Call `GET /x/tweets/search` and return an array of tweets. Call `GET /x/users/{id}` and return one user. Call `GET /x/trends` and return a trend list. Call `POST /x/tweets` and return created tweet metadata. Call `POST /extractions` and return the job ID and status. Call `POST /monitors` and return the monitor ID and status. Call `POST /webhooks` and return the webhook ID and signing secret. Example Search Tweets action: ```typescript theme={null} import xquik from "../../app/xquik.app"; export default { key: "xquik-search-tweets", name: "Search Tweets", description: "Search recent X posts with Xquik.", version: "0.0.1", type: "action", props: { xquik, apiKey: { propDefinition: [xquik, "apiKey"] }, q: { type: "string", label: "Query" }, limit: { type: "integer", label: "Limit", optional: true, default: 25 }, }, async run({ $ }) { const data = await this.xquik.request( $, { method: "GET", url: "/x/tweets/search", params: { q: this.q, limit: this.limit }, }, this.apiKey, ); $.export("$summary", `Found ${(data.tweets || []).length} tweets.`); return data.tweets || []; }, }; ``` ## Result Handoff Pipedream exports and source metadata pass stable fields between workflow steps. Destinations should receive normalized fields, not complete API responses. Export `tweet_count`, `has_more`, and `next_cursor`; return tweet rows with `tweet_id`, `text`, `author_username`, `created_at`, and optional `url`. Export `user_id`, `username`, `name`, `followers`, `verified`, and `profile_picture`; return one profile row for `GET /x/users/{id}`. Export `trend_count` and `woeid`; return trend rows with `name`, `rank`, `query`, and `description`, then keep the selected region with workflow event metadata. Send a unique `Idempotency-Key`. Export `id`, `status`, `billing`, `result`, and `statusUrl`. Poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. For tweets or replies, pass public URLs in `media` and export `tweet_id` or `write_action_id`. For DMs, upload first, pass one `media_id` in `media_ids`, export `message_id`, and leave `reply_to_message_id` unset. Export monitor `id`, `username`, `xUserId`, `eventTypes`, `isActive`, and `nextBillingAt`; export webhook `id`, `url`, `eventTypes`, and one-time `secret`. For Pipedream data stores, map production `deliveryId` to `delivery_id` for receiver retry de-dupe and `streamEventId` to `stream_event_id` when one monitor event should process once across endpoint changes. Emit `deliveryId` for endpoint-level retry de-dupe, `streamEventId` for event-level de-dupe across endpoint changes, and `occurredAt` as `ts`. Call `GET /api/v1/events` with `cursor` when a workflow needs replay. Export `event_id`, `type`, `monitor_id`, `monitor_type`, `occurred_at`, `has_more`, and `next_cursor`. Return `2xx` after accepting duplicate `deliveryId` or `streamEventId`; keep endpoint signing values, raw request body, raw signature, and full headers out of step exports, logs, data stores, Slack messages, CRM rows, and retry queues. Emit completed job `id`, `tool_type`, and `status`; fetch detail rows and carry `has_more` plus `next_cursor` into warehouse batches. ## Source 1: Monitor Event Webhook This source delivers immediate tweet, reply, quote, and repost alerts. Setup flow: 1. Pipedream creates an HTTP endpoint for the source. 2. The source calls `POST /webhooks` with that endpoint and selected event types. 3. The user creates or selects an Xquik monitor. 4. Each webhook payload emits one event with a stable ID. Payload mapping: Map Pipedream `id` to `streamEventId` for event-level de-dupe, or `deliveryId` for endpoint-level de-dupe. Map `eventType` to route `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet` events. Map `occurredAt` as the event timestamp. Map `username` for account monitor events. Map `data.id` as the tweet identifier. Map `data.text` as the tweet body. Map `data.author.userName` when present. Use `username` as the monitored-account fallback. Keep the webhook secret returned by Xquik and verify `x-xquik-signature` before emitting events. ## Build Event-Driven Twitter Workflows Event driven workflows start when Xquik delivers a monitor event. Account monitors can identify new tweets, replies, quotes, and reposts. Keyword monitors can identify matching tweets without repeated broad searches. Verify HMAC against the exact request body before parsing JSON. Reject stale timestamps and reused nonces before starting downstream steps. Use `deliveryId` for endpoint retry deduplication. Use `streamEventId` for event deduplication across endpoint changes. Pipedream data stores can retain these identifiers with expiration times. Their operations are not atomic transactions. Design every CRM upsert and Slack notification for repeated delivery. Return `2xx` after accepting an already processed event. Real-time synchronization should preserve the event timestamp and monitor ID. Separate filters should route replies, quotes, and reposts. This separation gives marketing campaigns and support teams clearer alerts. A Twitter monitor webhook should send only verified event fields downstream. Keep raw bodies and signature headers out of workflow exports. Use stored event replay when a destination recovers after downtime. ## Source 2: Extraction Completed Polling Use this when teams want batch jobs without webhook setup. ```typescript theme={null} export default { key: "xquik-extraction-completed", name: "Extraction Completed", description: "Emit completed Xquik extraction jobs.", version: "0.0.1", type: "source", props: { xquik, apiKey: { propDefinition: [xquik, "apiKey"] }, timer: { type: "$.interface.timer", default: { intervalSeconds: 900 } }, }, async run() { const data = await this.xquik.request( this, { method: "GET", url: "/extractions", params: { status: "completed", limit: 25 }, }, this.apiKey, ); for (const job of data.extractions || []) { this.$emit(job, { id: job.id, summary: `Extraction ${job.id} completed`, ts: Date.parse(job.completedAt || job.updatedAt || job.createdAt), }); } }, }; ``` ## Recipes ### Search Tweets To Slack Run the workflow on the reporting cadence. Call the Search Tweets action and return recent matching posts. Keep only tweets that meet the minimum engagement threshold. Send the selected tweet text, author, and link to the channel. ### Monitor Events To CRM Receive Xquik monitor events from the webhook source. Route `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet` events separately. Enrich the event with the Get User action before CRM routing. Upsert by user ID to avoid duplicate account records. ### Extraction To Warehouse Start the extraction job with `POST /extractions`. Poll for completed jobs before loading rows downstream. Fetch the completed extraction detail and result rows. Send normalized rows to the warehouse destination. ## Automate Focused Twitter Workflows Use tweet search automation for scheduled brand, topic, or competitor searches. Send matched tweets to Slack only after an engagement filter passes. The deduplication step uses persisted tweet IDs to suppress repeated alerts. Use monitor events for near-real-time lead generation signals. Enrich the author profile before creating a CRM record. Upsert by X user ID, not a mutable username. Each CRM record should include the triggering tweet URL. Use bounded follower pages for Google Sheets exports. Use extraction jobs when exports exceed one bounded page. Load completed rows in batches and preserve the next cursor. Use scheduled tasks for regional trends and recurring tweet searches. Keep schedule frequency within documented Twitter API rate limits. Record the search window so later runs avoid overlapping results. The approval queue governs every blog post and scheduled tweet. An RSS item can create a draft payload. A reviewer should approve its text, links, and target account. The final action can then publish with an idempotency key. These automation tools should reduce repetitive copying. They should not automate unsolicited replies, follows, or direct messages. ## Test Coverage Add focused tests before sharing the component package: Every request includes `x-api-key` and never logs the key. `401` produces "Authentication failed. Check the Xquik API key." `429` includes `Retry-After` when present. Returns an array with stable tweet IDs. Sends callback URL and selected event types. Emits one event per payload with a stable ID. Emits only completed extraction jobs. Run component tests and publish privately first: ```bash theme={null} npm test pd publish components/xquik/actions/search-tweets/search-tweets.ts ``` ## Pipedream Twitter Automation Questions ### What Is Pipedream Automation? Pipedream automation connects triggers, API calls, code, and cloud applications. Xquik adds tweet, profile, follower, monitor, and publishing operations. ### How Do I Set Up Pipedream Workflow Automation? Create one trigger, add an Xquik action, then test its output. Add the destination only after the Xquik response is stable. ### How Do I Connect a Twitter Webhook to Custom Code? Use an HTTP source or trigger that preserves the exact request body. Verify HMAC, timestamp, and nonce before running custom code. ### Which Trigger Fits Tweet Search Automation? Use a schedule for periodic searches. Use a monitor webhook for near-real-time account or keyword events. ### Can Pipedream Connect Twitter Events to Cloud Apps? Pipedream can send verified events to Slack, Sheets, CRMs, or warehouses. Each handoff should normalize tweet and profile fields. ### How Do Serverless API Integrations Handle Rate Limits? Honor `Retry-After`, cap concurrency, and retry only safe operations. Persist cursors after the destination confirms success. ### Can Pipedream Run Scheduled Twitter Tasks? Pipedream can schedule searches, trend reads, and extraction polling. Place tweet publishing behind a separate human approval step. ### How Do Pipedream CRM Integrations Avoid Duplicate Leads? CRM integrations should upsert profiles by X user ID. Deduplicate monitor events by `streamEventId` before creating CRM activity. ### Does This Pipedream Twitter Guide Need a Native Twitter App? The component calls Xquik's REST API with an Xquik API key. The workflow does not depend on a native Twitter app. ### When Should I Create a Private Pipedream Component? Create one when team members repeat the same authenticated request. Use inline code for a single experimental workflow. ### How Should Pipedream Store Webhook Deduplication Keys? Store delivery and event IDs separately with suitable expiration times. Keep downstream writes idempotent because store operations are not transactional. ### What Are Common Pipedream Twitter Automation Use Cases? Common cases include searches, profile enrichment, follower exports, and monitor alerts. Other cases include trend reports, extraction loads, and approved posts. ## Pipedream and Xquik Sources * [Pipedream Workflows](https://pipedream.com/docs/workflows) * [Pipedream HTTP requests](https://pipedream.com/docs/workflows/building-workflows/http) * [Pipedream workflow triggers](https://pipedream.com/docs/workflows/building-workflows/triggers) * [Pipedream components](https://pipedream.com/docs/components) * [Pipedream data stores](https://pipedream.com/docs/workflows/data-management/data-stores) * [Pipedream workflow errors](https://pipedream.com/docs/workflows/building-workflows/errors) * [Pipedream concurrency and throttling](https://pipedream.com/docs/workflows/building-workflows/settings/concurrency-and-throttling) ## Next Steps * Read [API Reference](/api-reference/overview) for auth, rate limits, and errors. * Read [Webhooks](/webhooks/overview) for payload shape and retries. * Read [Extraction Workflow](/guides/extraction-workflow) for job creation and result pagination. * Choose Make or Zapier when the team wants no-code scenario builders. # Twitter Search Pipeline with Python & Prefect Source: https://docs.xquik.com/guides/prefect Build scheduled Twitter search, profile, timeline, and trend workflows in Python with Prefect. Paginate tweet and user pages, then retry failures. See examples.
For the complete documentation index, see llms.txt.
Prefect is an open-source workflow orchestrator for Python code. This Prefect Python tutorial schedules Twitter API Python reads through Xquik. It builds a Prefect Python workflow orchestration pipeline for tweets, profiles, timelines, and trends. Use [`prefect-xquik`](https://github.com/Xquik-dev/prefect-xquik) for repeatable tweet searches, profile lookups, timeline refreshes, and trend checks. Use it for scheduled Twitter API reads in Python. This Prefect Python library gives you typed credentials and six read-only tasks. This collection is not a general Prefect Python SDK. The collection is read-only. It provides six asynchronous Prefect tasks. Each task returns the canonical Xquik JSON response as a Python dictionary. You can track Python functions as data pipelines with Prefect. Prefect flows wrap normal Python code with retries, schedules, and state tracking. Prefect offers run history, deployment controls, and execution environments for each data workflow. The Prefect UI shows every created task and flow state in real time. Search keywords, hashtags, accounts, dates, and X query operators. Fetch one public tweet from its numeric ID. Find public X accounts by name, username, or topic. Retrieve one public profile by username or user ID. Fetch recent tweets with optional replies and parent context. Retrieve worldwide or regional trending topics by WOEID. Use this collection for research, enrichment, dashboards, alerts, and indexing. Use direct REST, SDK, or MCP calls for writes, monitors, webhooks, and exports. ## Install a Verified Prefect Release Use Python 3.10 or newer. Pin the verified source tag and Prefect release. ```bash theme={null} python -m pip install \ "prefect==3.4.25" \ "importlib-metadata==8.7.0" \ "prefect-xquik @ https://github.com/Xquik-dev/prefect-xquik/archive/refs/tags/v0.1.7.tar.gz" ``` You can install `prefect-xquik` 0.1.8 or earlier releases from PyPI. Use the tested `v0.1.7` source pin with Prefect 3. It also uses the canonical Xquik API base URL. Tag `v0.1.8` only changes package maintenance. It also requires newer Prefect releases. The explicit `importlib-metadata` pin fixes Prefect's clean Python 3.12 CLI import. Prefect documents the upstream issue in [#22011](https://github.com/PrefectHQ/prefect/issues/22011) and [#22137](https://github.com/PrefectHQ/prefect/issues/22137). Use the tested pins above for production. Use a separate sandbox for compatibility checks. ```bash theme={null} python -m venv .venv-prefect-compat source .venv-prefect-compat/bin/activate python -m pip install -U prefect prefect-xquik prefect server start ``` Keep the server running. Open another shell and activate the same environment. ```bash theme={null} source .venv-prefect-compat/bin/activate prefect config set PREFECT_API_URL="http://127.0.0.1:4200/api" prefect block register -m prefect_xquik ``` PowerShell users should activate `.venv-prefect-compat\Scripts\Activate.ps1` instead. Run `pip install -U prefect` only inside this sandbox. Run `prefect server start` before registering the block. Keep the tested pins above for this integration. Treat Prefect and the collection as separate Python packages. ## Store the Xquik API Key Create an [Xquik API key](/x-api-quickstart). Store it inside an `XquikCredentials` block. ```python theme={null} from prefect_xquik import XquikCredentials credentials = XquikCredentials( api_key="xq_YOUR_KEY_HERE", base_url="https://xquik.com/api/v1", api_contract="2026-04-29", timeout_seconds=30, ) credentials.save("xquik", overwrite=True) ``` Prefect blocks store typed configuration across flows and deployments. The API key uses Pydantic `SecretStr`. Prefect hides its value in normal block rendering. Never place API keys in deployment YAML, flow parameters, logs, or repositories. Limit block access to deployments that require Xquik reads. ## Understand the Prefect Python Runtime Prefect runs ordinary Python functions. The `@flow` decorator creates a tracked flow run. The `@task` decorator creates task state with optional retries. Configure retries per task or through a nonzero global default. The `from prefect import flow, task` statement imports both decorators. A dynamic workflow can branch from returned tweets, profiles, or trends. Prefect does not require YAML files for Python flows. Use Python code for branches, loops, and task dependencies. Store the local profile name or server URL in environment variables. Store Xquik keys in `XquikCredentials` for deployed flows. ## Choose the Right Prefect Task Calls `GET /x/tweets/search`. Accepts `query`, `cursor`, `limit`, `query_type`, `since_time`, and `until_time`. Calls `GET /x/tweets/{id}`. Pass one numeric tweet ID. Calls `GET /x/users/search`. Accepts a profile query and optional cursor. Calls `GET /x/users/{id}`. Accepts usernames with or without `@`. Calls `GET /x/users/{id}/tweets`. Supports replies, parent tweets, and cursors. Calls `GET /x/trends`. Accepts `woeid` and a count from 1 through 50. `search_tweets` limits each request to 200 tweets. `get_trends` limits each request to 50 topics. The collection validates these ranges before sending a request. ## Build a Twitter Automation Flow in Python This flow searches recent Prefect posts and normalizes tweet rows. It preserves IDs, authors, timestamps, metrics, URLs, and pagination state. ```python theme={null} from __future__ import annotations from datetime import datetime, timedelta, timezone from typing import Any, Optional from prefect import flow, task from prefect_xquik import XquikCredentials, search_tweets @task async def normalize_tweet_page( page: dict[str, Any], ) -> list[dict[str, Any]]: rows: list[dict[str, Any]] = [] for tweet in page.get("tweets", []): if not isinstance(tweet, dict): continue author = tweet.get("author") rows.append( { "tweet_id": tweet.get("id"), "text": tweet.get("text"), "created_at": tweet.get("createdAt"), "url": tweet.get("url"), "author": author if isinstance(author, dict) else {}, "like_count": tweet.get("likeCount"), "repost_count": tweet.get("retweetCount"), "reply_count": tweet.get("replyCount"), } ) return rows @flow(name="Xquik Twitter Search") async def social_signal_flow( since_time: Optional[str] = None, until_time: Optional[str] = None, ) -> dict[str, Any]: if (since_time is None) != (until_time is None): raise ValueError("Pass both time boundaries or neither boundary") if since_time is None and until_time is None: window_end = datetime.now(timezone.utc) window_start = window_end - timedelta(hours=1) since_time = window_start.isoformat().replace("+00:00", "Z") until_time = window_end.isoformat().replace("+00:00", "Z") credentials = XquikCredentials.load("xquik") page = await search_tweets( credentials, query='"workflow orchestration" lang:en -filter:retweets', query_type="Latest", since_time=since_time, until_time=until_time, limit=100, ) rows = await normalize_tweet_page(page) return { "tweet_rows": rows, "has_more": page.get("has_next_page", False), "next_cursor": page.get("next_cursor"), } ``` Run it with explicit ISO 8601 boundaries. ```python theme={null} import asyncio result = asyncio.run( social_signal_flow( since_time="2026-08-01T00:00:00Z", until_time="2026-08-02T00:00:00Z", ) ) ``` The timestamps define a reproducible search window. Store them beside every run checkpoint. ## Write Focused Tweet Search Queries Narrow each search to avoid irrelevant rows. Use `"workflow orchestration"` for an exact phrase. Use `from:PrefectIO` for tweets from one account. Use `#prefect #python` for matching hashtags. Use `prefect min_faves:10` for a like threshold. Use `since:` and `until:` dates inside a query. Use `-filter:retweets` when original posts matter. Use `query_type="Latest"` for chronological monitoring. Use `query_type="Top"` for engagement-ranked discovery. Rankings can change, so persist tweet IDs. ## Schedule the Twitter Search Pipeline Use `.serve()` for a long-running local process. Add a cron schedule and timezone. ```python theme={null} from prefect.schedules import Cron if __name__ == "__main__": social_signal_flow.serve( name="xquik-social-signals", schedule=Cron("0 * * * *", timezone="UTC"), ) ``` The scheduled flow derives the previous hour on every run. Pass explicit boundaries for backfills. Use a work-pool deployment for Docker, Kubernetes, or serverless workers. ```bash theme={null} prefect deploy social_signal_flow.py:social_signal_flow \ --name xquik-social-signals \ --pool production ``` Prefect schedules create flow runs. Workers execute those runs. Confirm that the selected work pool has an active worker. ## Paginate Tweet and Profile Results Cursor pagination continues large searches without guessing page numbers. Keep the original request unchanged. Pass only the returned cursor. ```python theme={null} from typing import Any, Optional from prefect import flow from prefect_xquik import XquikCredentials, search_tweets @flow async def collect_tweet_pages(query: str) -> list[dict[str, Any]]: credentials = XquikCredentials.load("xquik") rows_by_id: dict[str, dict[str, Any]] = {} cursor: Optional[str] = None while True: page = await search_tweets( credentials, query=query, query_type="Latest", limit=100, cursor=cursor, ) for tweet in page.get("tweets", []): if isinstance(tweet, dict) and tweet.get("id"): rows_by_id[str(tweet["id"])] = tweet cursor_value = page.get("next_cursor") has_more = bool(page.get("has_next_page", False)) if not has_more or not cursor_value: break cursor = str(cursor_value) return list(rows_by_id.values()) ``` Do not decode cursors. Treat them as opaque strings. Store each cursor after persisting its page. Resume from that checkpoint after a worker restart. Use the same pattern with `search_users` and `get_user_tweets`. Keep `query`, `query_type`, `limit`, replies, and timestamps unchanged. ## Make Scheduled Runs Idempotent Scheduled windows can overlap. Workers can also retry completed requests. Prevent duplicate downstream rows with stable identifiers. Upsert tweet rows by `id`. Do not use text as a key. Upsert profile rows by numeric user `id`. Store `since_time`, `until_time`, query, and ordering. Store `has_next_page` and `next_cursor` after each committed page. Keep tweet rows, profile rows, trend rows, and checkpoints separate. This simplifies retries and schema evolution. ## Retry Only Transient Failures Prefect supports retry delays, jitter, and conditional retries. Do not retry invalid inputs or billing failures unchanged. ```python theme={null} from prefect_xquik import XquikError, search_tweets def retry_transient_xquik_error(_task, _task_run, state) -> bool: try: state.result() except XquikError as error: return error.status_code is None or error.status_code in {424, 429, 502} return False search_recent_tweets = search_tweets.with_options( name="Search Recent Tweets", retries=4, retry_delay_seconds=[5, 15, 45, 120], retry_jitter_factor=0.5, retry_condition_fn=retry_transient_xquik_error, ) ``` Add jitter so scheduled tasks avoid synchronized retries. The collection does not expose response headers on `XquikError`. Use conservative delays for `429`, or call REST directly when header-aware handling matters. ## Route Every Documented Error `XquikError` exposes a sanitized message, optional `status_code`, and raw `response_text`. Do not persist the raw response. Store the status, task run ID, endpoint, and safe error category. Fix missing queries, invalid limits, or malformed input. Do not retry unchanged. Load a valid Xquik API key from the credentials block. Resolve subscription or credits before the next scheduled run. Check tweet IDs, usernames, and user IDs. Search routes omit this status. Retry with bounded backoff. Stop after the configured attempt cap. Slow the schedule and retry with jittered delays. Retry transient X retrieval failures with a cap. Retry bounded connection failures when `status_code` is absent. These statuses cover all six collection endpoints. Do not add undocumented statuses to route-specific retry rules. ## Control Concurrency and Rate Limits Multiple schedules can overlap and share one API key. Create one global rate limit with slot decay. ```bash theme={null} prefect gcl create xquik-read-rate --limit 300 --slot-decay-per-second 300 ``` Acquire one shared slot before every Xquik read. ```python theme={null} from prefect.concurrency.asyncio import rate_limit async def acquire_xquik_read_slot() -> None: await rate_limit("xquik-read-rate", occupy=1, strict=True) ``` Await this helper before `search_tweets`, `get_tweet`, and `search_users`. Also await it before `get_user`, `get_user_tweets`, and `get_trends`. Use a separate Prefect concurrency limit for in-flight task runs. Concurrency limits only cap active work. They do not set request frequency. Keep retry policies aligned with [Xquik rate-limit guidance](/guides/rate-limits). Read endpoints share a 300 per 1s user bucket. Treat this as one shared capacity limit. Use one limit across tweet search, profile lookup, timelines, and trends. Make every task use that same account limit. Slow low-priority refreshes before delaying interactive reads. ## Build Profile and Timeline Workflows Use `search_users` when a name or topic can match multiple accounts. Use `get_user` when you already know the username or user ID. `get_user_tweets` retrieves one account's recent timeline. Set `include_replies=True` for conversations. Set `include_parent_tweet=True` when reply context matters. Normalize profiles into stable fields: * `id` * `username` * `name` * `followers` * `following` * `verified` * `profilePicture` Normalize tweets separately. Preserve `id`, `text`, `author`, `createdAt`, metrics, and `url`. ## Build Regional Trend Alerts `get_trends(credentials, woeid=1, count=30)` returns worldwide trends. Pass another valid WOEID for a region. Store each trend's `name`, `rank`, `query`, and `description` when present. Store response `woeid` and `count` with the alert batch. Trends are discovery signals, not verified facts. Validate important topics through tweet search before alerting customers. ## Result Handoff Store `tweets`, `has_next_page`, and `next_cursor`. Normalize tweets before loading a warehouse or dashboard. Store `users`, `has_next_page`, and `next_cursor`. Keep numeric user IDs as primary keys. Store `trends`, `count`, and `woeid`. Preserve each trend's rank. Store status, endpoint, task run ID, attempt count, and safe error category. Do not pass raw dictionaries directly into alerts or generators. Define compact row contracts first. Keep row fields stable when API responses add fields. ## Prefect Collection or Direct Xquik API Choose `prefect-xquik` for its six supported reads. It provides blocks, async calls, validation, and task metadata. Use direct REST, a generated SDK, or MCP for: * Tweet, reply, quote, repost, like, follow, or DM actions * Follower and following exports * Tweet replies, quotes, reposts, lists, and communities * Monitors, signed webhooks, and stored event replay * CSV, JSON, XLSX, Markdown, or PDF extraction jobs * Request filters absent from the six Prefect tasks Keep writes behind explicit approval. Store confirmed IDs before retrying any write action. ## Common Prefect Twitter API Questions ### What Is Prefect Python? Prefect orchestrates Python functions as tracked flows and tasks. It adds schedules, retries, state, logs, and deployment controls without replacing Python syntax. ### What Does This Python Prefect Tutorial Cover? It builds a Prefect Python pipeline for scheduled Twitter searches and timeline reads. The flow preserves tweets, cursors, time windows, and documented errors. ### How Does a Twitter Search API Python Flow Work? The flow calls `search_tweets` with a query and time window. It normalizes tweet IDs, text, authors, metrics, URLs, and pagination fields. ### Is This Twitter Automation Python Workflow Read-Only? Yes. The Prefect Python API collection reads tweets, profiles, timelines, and trends. Send approved writes through separate REST, SDK, or MCP routes. ### Is `prefect-xquik` a Prefect Python SDK? No. You get six asynchronous read tasks. Use Xquik SDKs when a workflow needs broader REST coverage. ### Which Prefect Python Version Should I Install? Use Python 3.10 or newer with the tested Prefect 3.4.25 pin. Validate newer Prefect releases before changing production dependencies. ### Where Is the Prefect Python GitHub Collection? The [prefect-xquik repository](https://github.com/Xquik-dev/prefect-xquik) contains the package, examples, tests, and release tags. Check source changes before upgrading your pinned deployment. ### Python Prefect vs Airflow: Which Fits Twitter Search? Pick Prefect when Python control flow and task retries matter. Choose Airflow when your team already runs DAG-based schedules. Either system still needs cursor, idempotency, and rate-limit controls. ### How Do I Automate Twitter Search With Python? Install `prefect-xquik`, register `XquikCredentials`, then schedule `search_tweets`. Use explicit time windows and cursor checkpoints. ### Can Prefect Schedule Twitter Profile and Timeline Reads? Yes. Use `get_user` for profiles and `get_user_tweets` for recent timelines. Enable replies only when needed. ### Can the Prefect Collection Post Tweets? No. The six tasks are read-only. Use Xquik REST, SDK, or MCP routes for approved writes. ### How Do I Handle Twitter API Rate Limits in Prefect? Await the shared rate limiter before all six tasks. Retry `429` with jittered delays and a hard attempt cap. Keep concurrency limits separate. ### How Do I Prevent Duplicate Tweets Across Scheduled Runs? Enforce a unique tweet ID constraint. Upsert rows or ignore conflicts during overlapping runs. Save time windows and cursors after each committed page. ### Should I Use Prefect or Cron for Twitter Automation? Use cron for one simple local script. Use Prefect for retries, blocks, schedules, workers, run history, and deployment controls. ### Where Should I Store the Xquik API Key? Store it in an `XquikCredentials` block. Never pass it as a flow parameter. ## Source and Next Steps * [prefect-xquik source](https://github.com/Xquik-dev/prefect-xquik) * [prefect-xquik v0.1.7](https://github.com/Xquik-dev/prefect-xquik/releases/tag/v0.1.7) * [prefect-xquik on PyPI](https://pypi.org/project/prefect-xquik/) * [Prefect schedules](https://docs.prefect.io/v3/concepts/schedules) * [Prefect task retries](https://docs.prefect.io/v3/how-to-guides/workflows/retries) * [Tweet Search API](/api-reference/x/search-tweets) * [User Timeline API](/api-reference/x/user-tweets) * [Rate Limits](/guides/rate-limits) * [Error Handling](/guides/error-handling) # Pydantic AI MCP Twitter API Agent Guide for Python Source: https://docs.xquik.com/guides/pydantic-ai Build a Pydantic AI Twitter API agent for typed tweet search, profiles, followers, monitors, exports, and reviewed X actions through MCP. See Python code.
For the complete documentation index, see llms.txt.
Build a Pydantic AI Twitter API agent through Xquik's MCP server. Search tweets, inspect profiles, export followers, and replay monitor events. Review every proposed post, reply, like, repost, follow, and direct message. Validate every durable handoff with a Pydantic model. ## Why Use Pydantic AI With a Twitter API? Pydantic AI combines model tool calls with typed Python output. Xquik supplies the Twitter API routes through two MCP tools: `explore` and `xquik`. Use each strength at the correct boundary. | Boundary | Pydantic AI control | Twitter agent benefit | | -------------------- | ---------------------- | -------------------------------------------- | | MCP connection | `MCP` capability | Keep credentials and tracing in your process | | Final response | Pydantic `output_type` | Reject malformed tweet rows and cursors | | Tool catalog | `.defer_loading()` | Hide unused MCP tools until discovery | | Write actions | `.approval_required()` | Pause before posting, replying, or following | | MCP naming | `.prefixed()` | Prevent tool collisions across servers | | Connection lifecycle | `async with agent` | Reuse one MCP session across related calls | Use one typed agent for tweet search, profile enrichment, or follower exports. Use another agent for monitor processing. Keep each agent focused. ## Pydantic AI Twitter API Prerequisites * Python 3.10 or later * An [Xquik API key](/x-api-quickstart) beginning with `xq_` * A Pydantic AI-supported model with tool calling * A connected X account for private reads or write actions Public X reads do not require X Developer credentials. Authenticate with Xquik. Connect an X account only when the selected route requires it. This supplies a Twitter API for Python agents through one MCP connection. Send the `x-api-key` header. Do not send bearer tokens or access tokens. ## Install Pydantic AI MCP Support Install the current stable Pydantic AI 2.x line. Keep FastMCP below 4 until Pydantic AI adds MCP SDK v2 support. ```bash theme={null} python -m pip install --upgrade \ "pydantic-ai-slim[anthropic,mcp]>=2.22,<2.23" \ "fastmcp-slim>=3.3,<4" \ python-dotenv ``` The explicit FastMCP range prevents an MCP SDK v2 prerelease from entering the environment. Remove that cap only after Pydantic AI adds FastMCP 4 support. This Pydantic AI MCP Streamable HTTP setup keeps execution inside Python. This Pydantic AI MCP server connection uses the Xquik endpoint. Keep the MCP URL HTTPS-only. It authenticates with the `x-api-key` header. After setup is complete, it can be run inside a local Python process. Store secrets outside source control. ```bash .env theme={null} XQUIK_API_KEY=xq_YOUR_KEY_HERE ANTHROPIC_API_KEY=sk-ant-... ``` ```text .gitignore theme={null} .env xquik-*-handoff.json ``` ## Build a Pydantic AI MCP Example for Tweet Search Define the final tweet-search handoff before creating the agent. Pydantic AI validates the model output against this schema. To import `Agent` from Pydantic AI, use `from pydantic_ai import Agent`. Use type hints for every durable handoff field. Start the async example with `import asyncio`. The `typing` import supplies `Literal` for finite stop reasons. ```python theme={null} import asyncio import os from pathlib import Path from typing import Literal from dotenv import load_dotenv from pydantic import BaseModel from pydantic_ai import Agent from pydantic_ai.capabilities import MCP class TweetRow(BaseModel): tweet_id: str text: str author_username: str | None = None created: int | None = None url: str | None = None class TweetSearchHandoff(BaseModel): query: str route_used: str tweets: list[TweetRow] has_more: bool next_cursor: str | None = None stop_reason: Literal["complete", "requested_limit", "cursor_stalled"] async def main() -> None: load_dotenv() xquik_mcp = MCP( "https://xquik.com/mcp", headers={"x-api-key": os.environ["XQUIK_API_KEY"]}, allowed_tools=["explore", "xquik"], ) agent = Agent( "anthropic:claude-sonnet-4-6", capabilities=[xquik_mcp], output_type=TweetSearchHandoff, instructions=( "Use Xquik for Twitter API requests. Preserve exact IDs and cursors. " "Use GET /api/v1/x/tweets/search. Never invent missing tweet fields." ), ) result = await agent.run( "Search 25 recent tweets about Pydantic AI MCP. " "Return the query, route, tweet rows, cursor state, " "and an explicit stop reason." ) Path("xquik-pydantic-ai-handoff.json").write_text( result.output.model_dump_json(indent=2), encoding="utf-8", ) asyncio.run(main()) ``` `MCP` is Pydantic AI's primary MCP entry point. The connection runs locally unless you configure another execution mode. Headers, hooks, and traces remain inside your Python process. The URL selects Streamable HTTP. The allowed list contains only the public `explore` and `xquik` tools. Model Context Protocol (MCP) turns Xquik routes into model-callable tools. Pydantic builds JSON schemas from `output_type`. Use explicit keyword arguments for `headers`, `allowed_tools`, `capabilities`, and `output_type`. Pydantic AI opens and closes the connection automatically. Enter `async with agent` when several runs should share one connection. The agent discovers `explore` and `xquik`. Discover route requirements before selecting an unfamiliar endpoint. The Xquik sandbox invokes `xquik.request(...)` with validated route values. Xquik injects authentication separately. ## Validate Twitter API Fields Before Storage MCP returns normalized snake\_case fields. Date-time values use Unix seconds. Map REST `createdAt` to `created`, never `created_at`. Keep these source fields unchanged: * Tweet rows: `id`, `text`, `author`, `created`, and `url` * Profile rows: `id`, `username`, `name`, `description`, and `followers` * Page state: `has_more` and `next_cursor` * Error fields: `error.type`, `error.code`, and `error.message` * Write receipts: `tweet_id`, `write_action_id`, and `charged_credits` Map source `id` to `tweet_id` or `user_id` only in your output model. Never cast large IDs to floating-point numbers. Search and list routes return `has_more` and `next_cursor`. Pass `cursor` for tweet, profile, follower, reply, timeline, community, and list pagination. Keep the original query and filters unchanged. Events, draws, and extraction pages use `cursor`. Radar pages use `after`. Draft pages use `afterCursor`. Treat every cursor as opaque. Continue through an empty page when `has_more` stays true. Stop when no cursor exists. Stop after the server repeats a cursor. Return `cursor_stalled` with the number of collected rows. MCP output has a 24,000-character limit. Project only required fields. Use extraction exports when the workflow must persist every complete row. ## Build a Typed Twitter Agent Handoff Conversation text cannot safely resume a Python Twitter API job. Persist the validated fields required by the next worker. Store the query, route, tweet IDs, authors, `created`, URLs, `has_more`, `next_cursor`, and stop reason. Store `user_id`, `username`, `name`, `description`, `followers`, `verified`, and `profile_picture`. Store the source user, extraction ID, status, poll URL, requested format, and export completion state. Store the root tweet ID, reply IDs, parent IDs, cursor state, and coverage diagnostics. Keep nested replies separate. Store `monitor_id`, `event_id`, `type`, `occurred_at`, `has_more`, and `next_cursor`. Send the next cursor as `cursor`. Store `webhook_id`, `delivery_id`, and `stream_event_id`. Keep the webhook secret in a secret manager. Store `tweet_id` or `write_action_id`, `status`, `charged_credits`, `poll`, and the idempotency key. Use public URLs in `media` for tweets. Reserve uploaded `media_id` values for direct messages. Keep API keys, headers, webhook secrets, and raw signatures outside agent output. Never place private messages in shared traces. ## Reuse the MCP Connection Safely Wrap related calls in `async with agent`. One session fetches two tweet-search pages. ```python theme={null} async def collect_two_search_pages() -> None: async with agent: first_page = await agent.run( "Search 25 tweets about Pydantic AI MCP. Preserve the next cursor." ) cursor = first_page.output.next_cursor if not first_page.output.has_more or cursor is None: Path("xquik-pydantic-ai-page-1.json").write_text( first_page.output.model_dump_json(indent=2), encoding="utf-8", ) return second_page = await agent.run( f"Continue tweet search for {first_page.output.query!r}. " f"Use this exact cursor: {cursor!r}. Preserve IDs and cursor state." ) Path("xquik-pydantic-ai-page-2.json").write_text( second_page.output.model_dump_json(indent=2), encoding="utf-8", ) ``` One local MCP session connects as one identity. Do not share one session across users with different Xquik keys. Give every credential its own request-scoped MCP capability. ## Require Approval Before X Actions Read-only agents can search tweets automatically. Write-capable agents need a human decision before every X action. Review posts, replies, likes, reposts, follows, and direct messages. Xquik exposes all API calls through one aggregate `xquik` tool. Review and approve each validated Xquik tool invocation. Leave `explore` available without approval. ```python theme={null} from pydantic import BaseModel from pydantic_ai import Agent, DeferredToolRequests, DeferredToolResults from pydantic_ai.mcp import MCPToolset class ActionReceipt(BaseModel): route_used: str tweet_id: str | None = None write_action_id: str | None = None status: str charged_credits: int | None = None xquik = MCPToolset( "https://xquik.com/mcp", headers={"x-api-key": os.environ["XQUIK_API_KEY"]}, ) reviewed_xquik = xquik.approval_required( lambda _ctx, tool_def, _args: tool_def.name == "xquik" ) write_agent = Agent( "anthropic:claude-sonnet-4-6", toolsets=[reviewed_xquik], output_type=[ActionReceipt, DeferredToolRequests], instructions="Never execute an X action without explicit approval.", ) pending = await write_agent.run( "Draft a reply to tweet 123. Do not post before approval." ) if isinstance(pending.output, DeferredToolRequests): approvals: dict[str, bool] = {} for call in pending.output.approvals: decision = input(f"Approve {call.tool_name} with {call.args}? [y/N] ") approvals[call.tool_call_id] = decision.strip().lower() == "y" completed = await write_agent.run( message_history=pending.all_messages(), deferred_tool_results=DeferredToolResults(approvals=approvals), ) ``` Inspect the sandbox code before approval. Confirm the route, method, account, target ID, text, media, and idempotency key. Reject unexpected arguments. The CLI displays every validated tool request. Replace it with your application's review screen when needed. Do not use `approve_all=True` when the agent can write to X. Store the approval decision and tool call ID with the resulting action receipt. ## Build Twitter API Error Handling Treat error messages as typed error handling inputs. | Status | Meaning | Pydantic AI decision | | ------ | ---------------------------------------- | ---------------------------------------------- | | `400` | Invalid parameters or unsupported fields | Correct the request before retrying | | `401` | Invalid Xquik authentication | Stop and replace the credential | | `402` | Subscription or credit action required | Report choices and request confirmation | | `424` | Dependency failure or incomplete replies | Preserve partial rows and inspect retryability | | `429` | Twitter API rate limit reached | Respect `error.retry_after` and back off | | `502` | Temporary upstream failure | Retry safe reads with a bounded policy | A `402` never authorizes a purchase. Show the available payment choices. Wait for explicit confirmation before any supported account checkout action. Never recreate a pending write after a timeout. Poll the returned action ID. Retry only safe reads when `error.retryable` permits it. ## Defer and Prefix MCP Tools Xquik exposes only two aggregate tools. Deferred loading is optional for one Xquik connection. Use it when several servers create a larger tool catalog. ```python theme={null} xquik = MCPToolset( "https://xquik.com/mcp", headers={"x-api-key": os.environ["XQUIK_API_KEY"]}, ) agent = Agent( "anthropic:claude-sonnet-4-6", toolsets=[xquik.defer_loading()], ) ``` Prefix tools when another server also exposes `explore` or `xquik`. ```python theme={null} twitter_tools = MCPToolset( "https://xquik.com/mcp", headers={"x-api-key": os.environ["XQUIK_API_KEY"]}, ).prefixed("twitter") agent = Agent( "anthropic:claude-sonnet-4-6", toolsets=[twitter_tools], ) ``` The resulting names become `twitter_explore` and `twitter_xquik`. Update any approval predicate after adding a prefix. ## Choose Pydantic AI MCP or the REST API MCP and REST solve different integration problems. MCP adds a discoverable tool layer over documented Twitter API routes. | Requirement | Choose | Reason | | ------------------------------------------------ | --------------- | ----------------------------------------- | | A model selects tweet or profile operations | Pydantic AI MCP | The agent discovers `explore` and `xquik` | | Application code calls one known route | REST API | The request stays deterministic | | A human must review an X action | Pydantic AI MCP | Deferred tool approval pauses execution | | A scheduled follower export runs without a model | REST API | No model decision is required | | A typed agent hands work to Python | Pydantic AI MCP | `output_type` validates the handoff | Xquik validates the sandbox route, method, query, and body. Pydantic validates the final typed handoff. Keep both layers. Each layer enforces a separate boundary. ## Tested Pydantic AI Compatibility These versions were checked on August 3, 2026. | Package | Checked version | Supported range in this guide | | ------------------ | --------------- | ---------------------------------- | | Python | 3.10 or later | `>=3.10` | | `pydantic-ai-slim` | 2.22.0 | `>=2.22,<2.23` | | `fastmcp-slim` | 3.4.5 | `>=3.3,<4` through the `mcp` extra | [Pydantic AI issue 6661](https://github.com/pydantic/pydantic-ai/issues/6661) tracks FastMCP 4 and MCP SDK v2 support. Use the stable FastMCP 3.x line until that compatibility work ships. ## Pydantic AI Twitter API Questions ### Does Pydantic AI Support MCP? Yes. Install the `mcp` extra and create an `MCP` capability. Xquik uses the recommended Streamable HTTP transport. ### What Is the Difference Between Pydantic AI Tools and MCP? Your Python application registers local Pydantic AI tools. MCP tools come from a connected server and can change independently. Xquik publishes `explore` and `xquik` through MCP. ### Is Pydantic AI Better Than LangChain for a Twitter Agent? Choose Pydantic AI when typed Python handoffs are the primary boundary. Choose [LangChain and LangGraph](/guides/langchain) for durable graph orchestration. Both can call the same Xquik MCP tools. ### Can Pydantic AI Call the Twitter API? Yes. MCP exposes eligible routes for tweets, profiles, followers, monitors, extractions, and X actions. Authenticate with an Xquik API key. ### Can Pydantic AI Scrape Tweets With Python? Yes. Call `GET /api/v1/x/tweets/search` through the MCP tools. Preserve tweet IDs, authors, `created`, URLs, `has_more`, and `next_cursor`. ### How Do I Validate Twitter API JSON With Pydantic? Pass a `BaseModel` as the agent's `output_type`. Call `model_dump_json()` before storing each validated output. Never print `result.output` into logs or files. Validate `result.output` before storage. Never save conversational text as JSON. ### How Can I Use Pydantic With AI Model Validation Effectively? Model tweet IDs and cursors as strings. Mark a field optional only when its route omits that field. Use `Literal` for finite stop reasons. Prefer typed Pydantic models. Validate before storage, queues, exports, or handoff. ### Why Avoid a str Return? A structured result preserves tweet IDs, cursors, stop reasons, and errors. A plain string cannot prove field completeness or types. ### How Do I Import Agent From Pydantic AI? Use `from pydantic_ai import Agent`. Import `BaseModel` from `pydantic` and `Literal` from `typing`. Keep these imports separate from application tools. ### Does a Pydantic AI Twitter Agent Need X Developer Keys? No. Use an Xquik API key. Some private reads and write actions also require a connected X account. ### How Do I Post a Tweet With Pydantic AI? Use the matching X write route through `xquik`. Validate and approve each requested X write operation. Store the idempotency key and returned action ID. ### How Do I Handle Twitter API Rate Limits in Python? Read `error.retry_after` from a `429` response. Wait for that interval. Apply bounded backoff when retrying safe read requests. ### How Do I Paginate Tweet Search in Pydantic AI? Persist `has_more` and `next_cursor` in the output model. Reuse unchanged filters. Stop on completion, limits, or a stalled cursor. ### Can a Pydantic AI Agent Export Twitter Followers? Yes. Create an extraction, persist its ID, and poll its status. Export only after completion. Save the source user and format together. ### Can Pydantic AI Monitor Twitter Keywords? Yes. Create a monitor and webhook. Persist both identifiers together. Replay missed events through `GET /api/v1/events` with `cursor` pagination. Monitor delivery is asynchronous and cannot guarantee real-time events. ## Next Steps * Read the [official Pydantic AI MCP client guide](https://pydantic.dev/docs/ai/mcp/client/). * Review the [MCP tool contract](/mcp/tools). * Follow the [agent handoff checklist](/mcp/agent-handoff). * Build a [tweet search workflow](/guides/tweet-scraper-csv-export). * Add [monitor and webhook delivery](/guides/brand-monitoring-workflow). * Compare [Twitter API alternatives](/twitter-api-alternatives). Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. # Twitter API Rate Limits, 429 Errors & Retry-After Source: https://docs.xquik.com/guides/rate-limits Understand Xquik Twitter API rate limits, recover from 429 errors, honor Retry-After, pace tweet and follower requests, and safely resume cursor exports.
For the complete documentation index, see llms.txt.
Xquik applies fixed-window Twitter API rate limits per Xquik account. These limits protect tweet reads, profile lookups, writes, and deletions. Standard API keys for one account share the same method buckets. Separate read, write, and delete buckets reset independently. **Standard limits:** 300 reads per second, 120 writes per minute, and 60 deletes per minute. After `429`, wait for `Retry-After` before retrying. ## Twitter API Rate Limits at a Glance | Rate-limit signal | Meaning | Client action | | ------------------------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------ | | 300 reads per second | `GET`, `HEAD`, and `OPTIONS` share one account bucket. | Pace tweet, profile, follower, reply, and event reads below 300 requests per second. | | 120 writes per minute | `POST`, `PUT`, and `PATCH` share one account bucket. | Queue tweets, monitors, webhooks, and profile changes below 120 requests per minute. | | 60 deletes per minute | Every `DELETE` request uses the delete bucket. | Serialize cleanup work and preserve each deleted resource ID. | | `429 Too Many Requests` | An Xquik tier or action limit blocked the request. | Read the error code before choosing a retry delay. | | `Retry-After` header | The response supplies a wait in seconds. | Pause that bucket for the supplied duration. | | `retryAfter` or `retryAfterMs` | The JSON body supplies a retry duration. | Use the documented unit before resuming work. | `GET`, `HEAD`, and `OPTIONS` allow 300 requests per 1 second. `POST`, `PUT`, and `PATCH` allow 120 requests per 60 seconds. `DELETE` allows 60 requests per 60 seconds. The listed request count succeeds inside each window. The next request receives `429 rate_limit_exceeded`. A read burst does not consume write capacity. A write burst does not consume delete capacity. ### Action-Specific Twitter API Limits Some actions add a narrower bucket. That bucket applies beside the method tier. | Action | Route | Additional limit | Recovery | | ------------------------- | -------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------- | | Follow or remove follower | `POST /x/users/{id}/follow` and `POST /x/users/{id}/remove-follower` | 20 actions per minute and 400 per day, shared | Track both counts. Wait for `Retry-After` after `429`. | | Connect X account | `POST /x/accounts` | 10 attempts per 15 minutes | Wait the returned remaining seconds. The maximum wait is 900 seconds. | | Login cooldown | Account connection and reauthentication | Dynamic cooldown from the login attempt | Use `retryAfterMs` or `Retry-After`. | The follow bucket protects connected X accounts from rapid automation. It also covers follower removal. Reaching 400 actions blocks more attempts. Track the daily count instead of retrying every minute. ## How the Fixed Window Works Xquik uses fixed-window counters. It does not use a token bucket. The first counted request starts that bucket's window. The first request starts a 1-second or 60-second window. Each request increments its read, write, or delete counter. Requests above the bucket limit receive `429 Too Many Requests`. The complete counter resets when its fixed window expires. ```text theme={null} Read bucket: 300 requests per 1 second 0.000s First GET starts the window 0.600s Request 300 succeeds 0.700s Request 301 returns 429 with Retry-After: 1 1.000s The bucket resets 1.001s The next GET succeeds ``` The window starts with your first request. It does not follow wall-clock seconds. An early retry does not extend the existing window. It still returns `429`. Always follow the response instead of guessing the reset time. ## Read a 429 Rate-Limit Response An Xquik tier limit returns a structured JSON error. It also returns `Retry-After` in seconds. ```http theme={null} HTTP/1.1 429 Too Many Requests Retry-After: 1 Content-Type: application/json { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 } ``` | Response field | Unit | Meaning | | ----------------- | ------------ | ------------------------------------------------- | | HTTP status `429` | None | A request limit or cooldown blocked the request. | | `Retry-After` | Seconds | Minimum wait before retrying that operation. | | `retryAfter` | Seconds | JSON copy of the Xquik tier wait. | | `retryAfterMs` | Milliseconds | Login cooldown wait for account connection flows. | | `error` | String | The exact reason, such as `rate_limit_exceeded`. | Standard read throttles return `Retry-After: 1`. Standard write and delete throttles return `Retry-After: 60`. Account connection returns the remaining window. A login cooldown returns its own remaining duration. ### Retry-After Does Not Always Mean Rate Limited Inspect the status and error code together. | Status or code | Meaning | Correct action | | -------------------------- | ---------------------------------------------------- | ----------------------------------------------------------- | | `429 rate_limit_exceeded` | Xquik request bucket was exhausted. | Wait for `Retry-After`, then retry once. | | `429 x_rate_limited` | X throttled a connected account write. | Wait for `Retry-After` when present. Otherwise, back off. | | `429 x_daily_limit` | The connected X account reached a daily write limit. | Wait 24 hours before reusing that account. | | `429 login_cooldown` | A connection attempt triggered a cooldown. | Wait `retryAfterMs` or `Retry-After`. | | `502 x_api_rate_limited` | The read service was throttled upstream. | Retry in a few minutes with bounded backoff. | | `402 insufficient_credits` | The account cannot fund the requested results. | Add credits or request fewer tweets, followers, or replies. | | `202` with `Retry-After` | A durable write remains active. | Poll `statusUrl`. Do not resend the write. | | `503` with `Retry-After` | A temporary write or service state needs time. | Follow `safeToRetry`, `nextAction`, and the header. | Read [API Error Handling](/guides/error-handling) before retrying writes. An idempotency key prevents duplicate submission. It does not make every retry safe. ## Xquik Limits Versus Official X API Limits The phrase "Twitter API limits" can describe two separate systems. Xquik enforces the account buckets documented above. X also enforces upstream limits for connected accounts and service access. | Limit owner | Typical scope | Client-visible signal | | ------------------ | -------------------------------------------------- | ----------------------------------------------- | | Xquik | Account plus HTTP method tier | `429 rate_limit_exceeded` and `Retry-After` | | Xquik action guard | Follow, follower removal, connection, or login | `429` with an action-specific error or cooldown | | X write service | Connected X account and write action | `x_rate_limited` or `x_daily_limit` | | X read service | Upstream tweet, profile, follower, or reply access | Default v1 can return `502 x_api_rate_limited` | Official X API rate limits vary by endpoint and authentication context. They can apply per app, user token, or endpoint. See [X API rate limits](https://docs.x.com/x-api/fundamentals/rate-limits) for the current official tables. Do not copy official X limits into an Xquik client limiter. Use the Xquik values on this page. Handle upstream codes separately. ## Recover From API Rate Limit Exceeded Use bounded retries for idempotent reads. Preserve cursor state before waiting. Never run an unbounded retry loop. Check the HTTP status and exact `error` value. Prefer `Retry-After`. Fall back to `retryAfter` or `retryAfterMs`. Preserve the query, filters, limit, and cursor. Add a small random delay after the required wait. Stop after 3 attempts. Surface the final error to the caller. ### Node.js 429 Recovery This helper retries `GET` requests only. It does not retry writes. ```javascript theme={null} const MAX_ATTEMPTS = 3; function retryDelayMs(response, body) { const headerSeconds = Number.parseInt( response.headers.get("Retry-After") ?? "", 10, ); if (Number.isFinite(headerSeconds) && headerSeconds > 0) { return headerSeconds * 1_000; } if (Number.isFinite(body.retryAfter) && body.retryAfter > 0) { return body.retryAfter * 1_000; } if (Number.isFinite(body.retryAfterMs) && body.retryAfterMs > 0) { return body.retryAfterMs; } return 1_000; } async function getJsonWithRateLimit(url) { for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) { const response = await fetch(url, { headers: { "x-api-key": process.env.XQUIK_API_KEY }, }); if (response.status !== 429) { if (!response.ok) { throw new Error(`Request failed with HTTP ${response.status}`); } return response.json(); } const body = await response.json().catch(() => ({})); if (attempt === MAX_ATTEMPTS) { throw new Error(body.error ?? "rate_limit_exceeded"); } const jitterMs = Math.floor(Math.random() * 250); const waitMs = retryDelayMs(response, body) + jitterMs; await new Promise((resolve) => setTimeout(resolve, waitMs)); } } ``` The header wins when both values exist. Jitter spreads simultaneous workers. The 3-attempt cap prevents a stalled queue from hiding an outage. ## Resume Tweet Exports After 429 Tweet searches return `has_next_page` and `next_cursor`. Store the completed page and next cursor atomically. Do not advance the cursor after a failed request. ```javascript theme={null} const baseUrl = "https://xquik.com/api/v1/x/tweets/search"; let cursor; while (true) { const url = new URL(baseUrl); url.searchParams.set("q", "from:example launch"); url.searchParams.set("queryType", "Latest"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const page = await getJsonWithRateLimit(url); // Commit unique tweets[].id values and next_cursor together. await storeTweetPageAndCursor(page.tweets, page.next_cursor); if (!page.has_next_page || !page.next_cursor) break; cursor = page.next_cursor; } ``` Keep `q`, `queryType`, filters, and `limit` unchanged while resuming. Dedupe stored tweets by `tweets[].id`. A repeated page then remains harmless. See [Request-Efficient API Usage](/guides/request-efficient-api-usage#store-cursor-checkpoints) for more checkpoint patterns. ### Cursor Recovery Checklist | Checkpoint | Save after success | Reuse after 429 | | --------------- | ----------------------------------------------------- | ---------------------------- | | Search query | Exact `q` string | Yes | | Sort order | Exact `queryType` | Yes | | Filters | Author, date, media, language, and engagement filters | Yes | | Page size | Exact `limit` | Yes | | Incoming cursor | Cursor used for the blocked page | Yes | | Returned cursor | `next_cursor` from the completed page | Only after storing that page | | Tweet identity | Every `tweets[].id` | Use for dedupe | ## Pace Requests Before 429 Leave headroom below each documented limit. Headroom absorbs network timing and work from other API keys. It also protects shared serverless workers. ### Use a Shared Node.js Limiter This Bottleneck configuration reserves 10% read headroom. ```javascript theme={null} import Bottleneck from "bottleneck"; const readLimiter = new Bottleneck({ reservoir: 270, reservoirRefreshAmount: 270, reservoirRefreshInterval: 1_000, maxConcurrent: 5, }); const response = await readLimiter.schedule(() => fetch("https://xquik.com/api/v1/x/tweets/search?q=launch&limit=100", { headers: { "x-api-key": process.env.XQUIK_API_KEY }, }), ); ``` One in-memory limiter coordinates only one process. Use a shared queue across multiple servers, functions, or containers. ### Rate-Limiting Libraries Use [bottleneck](https://github.com/SGrondin/bottleneck) for shared queues, or [p-limit](https://github.com/sindresorhus/p-limit) for concurrency caps. Use [ratelimit](https://github.com/tomasbasham/ratelimit) with `pip install ratelimit`. Use [rate](https://pkg.go.dev/golang.org/x/time/rate) with `go get golang.org/x/time/rate`. A client scheduler can use another algorithm. Keep its output below Xquik's fixed-window limits. Configure one coordinator per Xquik account. ## Reduce Twitter API Requests The limit counts requests, not returned tweets or followers. Use each endpoint's largest suitable page size. One 100-tweet page uses one read request. One request per tweet would use 100 read requests. Request a full page. Store `next_cursor`, then continue the same query. Never restart from page one after a recoverable `429`. Batch tweet or profile IDs through the documented batch endpoints. One batch request uses fewer read slots than individual lookups. Let monitors deliver matching tweets through signed webhooks. Use event reads for backfills, reconciliation, and missed delivery checks. Cache names, usernames, profile images, and account settings. Refresh them on a schedule that matches your product needs. Multiple API keys do not multiply a standard account's limits. Route every worker through one account-aware queue. Use different queues for reads, writes, and deletes. This mirrors the independent server buckets. ## Monitor API Throttling Record enough context to explain each `429`. Never log API key values. | Metric or log field | Why it matters | | --------------------- | -------------------------------------------------- | | HTTP method and route | Identifies the exhausted method bucket. | | `error` code | Separates Xquik limits from upstream X throttling. | | `Retry-After` | Confirms the required pause. | | Attempt count | Detects unbounded retry behavior. | | Worker concurrency | Shows whether parallel jobs share excessive load. | | Query and cursor hash | Connects a retry to the same tweet export page. | | Completed tweet count | Confirms progress before throttling. | A rising Xquik `429` rate usually means excessive local concurrency. A rising `x_api_rate_limited` count indicates a different upstream condition. Keep those alerts separate. ## Twitter API Rate-Limit Questions ### What Does API Rate Limit Exceeded Mean? The client sent more requests than one active window allows. Xquik returns `429 rate_limit_exceeded` for its own exhausted bucket. Wait for `Retry-After`, then retry the same idempotent read. ### How Long Does a Twitter API Rate Limit Last? Xquik read windows last 1 second. Write and delete windows last 60 seconds. Connection safety windows last 15 minutes. Login cooldowns use dynamic waits. Official X endpoint windows differ from these Xquik limits. ### Why Am I Rate Limited Below 300 Reads? All standard keys for one Xquik account share the read bucket. Another worker, function, or server may consume the remaining requests. Centralize scheduling and reserve headroom below 300 requests per second. ### Do Multiple API Keys Increase My Rate Limit? No. Standard API keys resolve to the same Xquik account buckets. Use separate keys for access control, rotation, and auditability. Do not use them to bypass request limits. ### Does Retrying Early Reset the Window? No. An early retry does not extend or reset the fixed window. It still returns `429` until the original window expires. ### Does a Rate-Limited Request Consume Tweet Credits? An Xquik tier rejection happens before the endpoint performs its work. It does not collect new tweets, followers, profiles, or replies. Successful pages completed before the rejection keep their normal charges. ### Should I Retry a Tweet Write After 429? First inspect `error`, `statusUrl`, `terminal`, and `safeToRetry`. Poll an active durable action instead of resending it. Use a new idempotency key only when `safeToRetry` is `true`. ### How Do I Avoid Twitter API Rate Limits? Use larger pages, cursor checkpoints, batch routes, caches, and signed webhooks. Share one limiter across workers. Keep separate queues for each method bucket. Search tweets with filters, page limits, and cursor recovery. Classify billing, validation, dependency, and write lifecycle errors. Preserve tweet, follower, reply, and extraction cursors safely. # Twitter API Pagination, Batching & Tweet Exports Source: https://docs.xquik.com/guides/request-efficient-api-usage Paginate Twitter API tweets, timelines, followers, and searches. Batch IDs, prevent duplicates, resume cursors, manage retries, and export CSV, JSON, or XLSX.
For the complete documentation index, see llms.txt.
Use this Twitter API pagination guide for tweets, profiles, followers, and exports. Choose one matching route. Save every returned ID and cursor. Resume that checkpoint instead of repeating completed pages.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
* 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)
Quick answer: batch known IDs. Use profile timelines for one account. Search tweets for keywords. Use extractions for saved CSV, JSON, or XLSX. ## Choose the Smallest Twitter API Route The smallest matching route reduces duplicate tweets and unnecessary requests. Do not force every job through Twitter advanced search. | Collection job | Route | Page control | Required account | | ----------------------------- | --------------------------------------------- | ----------------------------- | ---------------------------- | | Fetch known tweet IDs | `GET /x/tweets` | Up to 100 IDs | No connected X account | | Fetch known user IDs | `GET /x/users/batch` | Up to 100 IDs | No connected X account | | Get tweets by one user | `GET /x/users/{id}/tweets` | `pageSize` and `cursor` | No connected X account | | Search tweets by keyword | `GET /x/tweets/search` | `limit` and `cursor` | No connected X account | | Export followers or following | `GET /x/users/{id}/followers` or `/following` | `pageSize` and `cursor` | No connected X account | | Read the home feed | `GET /x/timeline` | `cursor` and `seenTweetIds` | Connected X account | | Save a durable file | `POST /extractions` | `resultsLimit`, then `cursor` | Depends on the selected tool | Check each route before reusing a parameter name. Twitter API endpoints do not share one page-size parameter. Use `GET /api/v1/x/tweets?ids=...` for up to 100 comma-separated tweet IDs in one request. Use `GET /api/v1/x/users/batch?ids=...` for up to 100 comma-separated user IDs in one request. Use `GET /api/v1/x/users/{id}/tweets` for one user's profile timeline. Pass a username or numeric X user ID. Use `GET /api/v1/x/tweets/search` for keywords, hashtags, operators, date filters, and advanced search pages. Use `GET /api/v1/x/timeline` for the connected account's home timeline. Pass `cursor` and optional `seenTweetIds`. Use `POST /api/v1/extractions/estimate`, `POST /api/v1/extractions`, and `GET /api/v1/extractions/{id}/export` for CSV, JSON, or XLSX. ## Batch Known Tweet or Profile IDs Batch known IDs before starting a cursor loop. One batch accepts up to 100 comma-separated IDs. Duplicate user IDs preserve only their first position. ```bash Tweets theme={null} curl "https://xquik.com/api/v1/x/tweets?ids=1893710452812718080,1893704267862470862" \ -H "x-api-key: xq_YOUR_KEY_HERE" ``` ```bash Users theme={null} curl "https://xquik.com/api/v1/x/users/batch?ids=44196397,783214" \ -H "x-api-key: xq_YOUR_KEY_HERE" ``` Keep the submitted ID list beside each response. Match returned tweets or profiles by their stable string IDs. Retry only IDs missing from the completed response. Batch profile responses expose reconciliation fields. Inspect `requested_count`, `processed_count`, and `returned_count`. Also inspect `unavailable_ids` and `unprocessed_ids`. Do not treat an unavailable profile as a transient pagination failure. Batch lookups are single-page requests. Do not send an empty `next_cursor` into another batch request. ## Match Twitter Search, Timeline & Feed Intent These routes can return similar tweet objects. Their collection intent remains different. Use `/x/users/{id}/tweets` for one account's posts. Add `includeReplies` when replies belong in the result. Use `/x/tweets/search` for keywords, hashtags, operators, dates, or engagement filters. Use `/x/timeline` for one connected account's ranked home feed. It is not a keyword search route. Use profile timelines for complete account-oriented collection. Use tweet search when the query meaning matters most. Use home timelines for feed-style inboxes or routing. Plain `from:username` date searches can use timeline-oriented collection. Add keywords when ranked search semantics matter more. Keep `q`, filters, dates, and `queryType` unchanged across pages. ## Use the Correct Page-Size Parameter Page-size names vary across Xquik routes. Copy the parameter from that endpoint's API reference. | Route family | Request parameter | Current range or behavior | | ----------------------- | ----------------- | ---------------------------------------------------- | | Tweet search | `limit` | Up to 200 tweets; the server can paginate internally | | User tweets and replies | `pageSize` | Automatic 1 to 300; standard 1 to 100; default 20 | | Followers and following | `pageSize` | Automatic 20 to 300; standard 20 to 200; default 200 | | Home timeline | None | The source controls each page size | | Extraction results | `limit` | 1 to 1,000 stored rows; default 100 | A requested size is an upper bound. Filters, source availability, or credits can reduce returned rows. Continue while the response says another page exists. Larger pages reduce HTTP calls. Smaller pages reduce memory and checkpoint loss after failures. Choose the largest size your worker can safely store atomically. ## Use Extraction Jobs for Saved Files Use extractions for durable Twitter scraper API jobs. They support saved results and repeatable file handoffs. Send the planned tool, target, and `resultsLimit`. Review the estimate before creating the job. Call `POST /api/v1/extractions`. Store its job `id`, `status`, and poll path. Poll `GET /api/v1/extractions/{id}` until completion. Pass `nextCursor` back through `cursor` for more stored rows. Download CSV, JSON, Markdown, Markdown document, PDF, TXT, or XLSX. Check the export page's row and format limits first. Direct routes suit live application pages. Extractions suit durable exports, analysts, and asynchronous workflows. | Need | Prefer direct API | Prefer extraction | | -------------------------------- | --------------------------- | ----------------------- | | Show one live page | Yes | No | | Resume a short cursor loop | Yes | Optional | | Download CSV or XLSX | No | Yes | | Save a durable job record | No | Yes | | Stream more rows than a file cap | Yes, or stored result pages | Use stored result pages | Store the export format, job ID, row count, and completion state. Do not call the export route before the job completes. ## Store Cursor Checkpoints Store the request and response cursor together. Treat each cursor as an opaque string. Never decode, trim, or construct one. ```json theme={null} { "route": "/api/v1/x/tweets/search", "query": "from:username webhook OR SDK", "query_type": "Latest", "limit": 100, "cursor_sent": null, "has_next_page": true, "next_cursor": "DAACCgACGRElMJcAAA", "unique_tweet_count": 93, "last_saved_tweet_id": "1893710452812718080", "collected_at": "2026-08-02T20:30:00Z" } ``` Most X reads return `next_cursor` and accept `cursor`. Stored extraction pages return `nextCursor` and accept `cursor`. Events and draws also accept `cursor`. Radar accepts `after`. Drafts accept `afterCursor`. The normalized REST contract uses `has_more` and `next_cursor`. Each route still preserves its documented request parameter. Write the rows and checkpoint in one database transaction. Advance only after the row write succeeds. Keep the previous checkpoint until validating its replacement. ## Implement a Bounded Tweet Search Loop This TypeScript example preserves query intent across cursor pages. It also stops repeated cursors and duplicate tweet rows. ```typescript theme={null} type Tweet = { id: string; text?: string; }; type TweetPage = { tweets: Tweet[]; has_next_page: boolean; next_cursor?: string; }; type SearchCheckpoint = { cursor_sent: string | null; next_cursor: string | null; page_number: number; unique_tweet_count: number; }; type SearchCollectionResult = { complete: boolean; next_cursor: string | null; unique_tweet_count: number; }; async function collectTweetSearch( apiKey: string, query: string, savePage: (tweets: Tweet[], checkpoint: SearchCheckpoint) => Promise, ): Promise { const seenTweetIds = new Set(); const seenCursors = new Set(); let cursor = ""; for (let pageNumber = 1; pageNumber <= 50; pageNumber += 1) { const params = new URLSearchParams({ limit: "100", q: query, queryType: "Latest", }); if (cursor !== "") { params.set("cursor", cursor); } const response = await fetch( `https://xquik.com/api/v1/x/tweets/search?${params}`, { headers: { "x-api-key": apiKey } }, ); if (!response.ok) { throw new Error(`Tweet search failed with ${response.status}`); } const page = (await response.json()) as TweetPage; const uniqueTweets = page.tweets.filter((tweet) => { if (seenTweetIds.has(tweet.id)) { return false; } seenTweetIds.add(tweet.id); return true; }); const nextCursor = page.next_cursor || null; if ( page.has_next_page && nextCursor !== null && (nextCursor === cursor || seenCursors.has(nextCursor)) ) { throw new Error("Pagination stopped because the cursor repeated"); } await savePage(uniqueTweets, { cursor_sent: cursor || null, next_cursor: nextCursor, page_number: pageNumber, unique_tweet_count: seenTweetIds.size, }); if (!page.has_next_page) { return { complete: true, next_cursor: null, unique_tweet_count: seenTweetIds.size, }; } if (nextCursor === null) { return { complete: false, next_cursor: null, unique_tweet_count: seenTweetIds.size, }; } seenCursors.add(nextCursor); cursor = nextCursor; } return { complete: false, next_cursor: cursor, unique_tweet_count: seenTweetIds.size, }; } ``` Move status-specific recovery outside this collection function. Resume only after correcting the failed condition. ## Guard High-Volume Twitter API Pagination Bound every high-volume tweet scraper loop. A filter can produce an empty page before later matches. An empty page does not prove pagination finished. Set maximum rows, pages, elapsed time, and expected credits. Store tweets by tweet ID. Store profiles by user ID. Continue while the response reports more pages. Permit empty filtered pages when the cursor advances. Stop when the next cursor is missing, unchanged, or previously seen. Save both cursors, unique rows, and the last stable ID. Schedule multiple accounts or searches fairly. Fetch one bounded page slice from each stream. Then resume deeper cursors in later rounds. One large timeline should not starve every other target. For agent calls, return counts or bounded field projections. API MCP output is limited to 24,000 characters. Use REST, SDKs, or exports for every complete row. ## Resume Recurring Tweet Collection Do not restart recurring searches from their first page. Save the last accepted tweet timestamp and stable ID. For time-based searches, pass `sinceTime` and `untilTime`. Use a small overlap between runs. Then deduplicate overlapping tweets by tweet ID. For recurring account or keyword checks, consider monitors. Signed webhooks push matching tweet or profile events. The events API supports replay and reconciliation. Keep separate checkpoints for each route and query. Changing filters creates a different result stream. Never reuse a cursor after changing its query. The [official X pagination guide](https://docs.x.com/x-api/fundamentals/pagination) confirms two durable principles. Pagination tokens are opaque. Short pages can still have successors. Xquik exposes those principles through its documented cursor fields. ## Recover Without Losing the Cursor Use the HTTP status before deciding whether to retry. | Status | Meaning for this workflow | Correct next action | | ------ | ------------------------------------------------ | ----------------------------------------------------------- | | `400` | A parameter or query is invalid | Fix the request. Do not retry unchanged. | | `401` | Authentication failed | Replace or correct the credential. | | `402` | Credits cannot fund the requested work | Add credits or reduce future work. Resume the saved cursor. | | `404` | The requested user, tweet, or job is unavailable | Record the missing target. Stop that stream. | | `424` | An upstream dependency failed | Retry the same saved cursor with bounded backoff. | | `429` | A request bucket or cooldown was exceeded | Wait for `Retry-After`. Retry the same cursor. | | `502` | The X read dependency failed | Retry the same cursor with capped exponential backoff. | Never advance the checkpoint after a failed response. Never charge a failed page to the unique-row count. Record partial completion when a retry budget expires. ## Control Credits, Requests & Memory Estimate work before starting large exports. Multiply expected unique rows by the route's documented result cost. Keep rate-limit budgets separate from credit budgets. Requested rows can exceed affordable rows. A paid endpoint can return a smaller page. Zero affordable paid results return `402 insufficient_credits`. Use these controls before every large run: * Maximum unique tweets or profiles. * Maximum cursor pages. * Maximum elapsed time. * Maximum expected credits. * Maximum retry attempts per cursor. * Maximum in-memory rows before flushing. See [Twitter API rate limits](/guides/rate-limits) for request pacing. See [pricing and billing](/guides/billing) for metered result rules. ## Avoid Unnecessary Media Work Pass public media URLs directly when creating tweets. One public MP4 URL can also use the `media` field. Do not upload already public tweet media first. Upload media when a direct message needs a `mediaId`. DM writes accept one item in `media_ids`. Keep tweet media URLs separate from DM media IDs. ## Twitter API Pagination Questions ### How Do I Paginate Twitter API Tweets? Read `has_next_page` and save `next_cursor`. Send that value as the next request's `cursor`. Stop only when the response reports no next page. ### Can I Get Every Tweet in One API Request? No. Collection routes return bounded pages. Tweet search can request up to 200 results through `limit`. Larger collections still require cursors or extraction jobs. ### Why Did the API Return Fewer Tweets Than Requested? Page size is an upper bound. Filters, source availability, and remaining credits can reduce results. Continue whenever `has_next_page` remains true. ### How Do I Get Tweets by One User Efficiently? Use `GET /x/users/{id}/tweets` with `pageSize` and `cursor`. Add `includeReplies=true` only when replies belong in scope. Use search when keywords or advanced filters define the job. ### Should I Use a Cursor or a Timestamp? Use cursors within one continuous collection run. Use timestamps to define windows between recurring runs. Apply a small overlap and deduplicate by tweet ID. ### How Do I Prevent Duplicate Tweets Across Pages? Store every tweet ID in a unique index. Track cursors separately from tweet IDs. Reject repeated cursors before requesting another page. ### Should I Use Direct API Pages or a Twitter Export? Use direct pages for live application responses. Use extractions for durable CSV, JSON, or XLSX handoffs. Use stored extraction pages when streaming beyond file limits. ## Efficient Twitter API Usage Checklist * Choose the route matching IDs, users, search, feed, or export intent. * Batch up to 100 known tweet or user IDs. * Preserve exact filters throughout every cursor run. * Store rows and checkpoints atomically. * Count unique IDs instead of raw array lengths. * Continue through short or empty pages when cursors advance. * Stop missing, unchanged, or repeated cursors. * Bound pages, rows, time, credits, retries, and memory. * Use `resultsLimit` for extraction estimates and jobs. * Resume the same cursor after recoverable errors. * Use monitors and webhooks for recurring checks. * Keep tweet media URLs separate from uploaded DM media IDs. Search tweets by keywords, dates, authors, media, or engagement. Estimate, create, poll, paginate, and export durable jobs. Pace requests and recover from `429` responses safely. # Twitter API CSV, JSON & XLSX Export Formats Source: https://docs.xquik.com/guides/response-formats-exports Export tweets, followers, following, replies, profiles, and draw rows through JSON pages or CSV, JSON, XLSX, Markdown, PDF, and TXT files with checkpoints.
For the complete documentation index, see llms.txt.
Choose the output shape before wiring a worker, dashboard, spreadsheet, or archive. Xquik gives you live JSON pages for endpoint calls, saved JSON pages for extraction jobs, file exports for downstream tools, and draw exports for giveaway audits. This guide covers Xquik API result files. It does not download tweet images or videos. It also does not export your private X account archive.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
* 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)
Read [Read Data Richness](/guides/tweet-profile-api-fields) before choosing columns downstream. It lists every optional tweet, profile, and media field. The export endpoint selects a file format. Filter or select columns downstream. ## Choose the Output Shape Use endpoints such as `GET /api/v1/x/tweets/search`, `GET /api/v1/x/users/{id}/tweets`, and `GET /api/v1/x/users/{id}/followers` when your app needs fresh API results and endpoint-specific cursors. Use `GET /api/v1/extractions/{id}?limit=1000&cursor={nextCursor}` when a saved extraction job is the source of truth. The response includes `job`, `results`, `hasMore`, and optional `nextCursor`. Use `GET /api/v1/extractions/{id}/export?format=csv` when a downstream tool needs a downloaded file. Supported formats are `csv`, `json`, `xlsx`, `md`, `md-document`, `pdf`, and `txt`. Use `GET /api/v1/draws/{id}/export?format=csv&type=winners` when you need giveaway winners or entry rows. Set `type` to `winners` or `entries`. ## Output Decision Map Call the live JSON endpoint and store the endpoint cursor with the filters that produced the page. Read saved extraction pages, append JSON Lines, and resume with `cursor` when `hasMore` is true. Download CSV for simple imports, or XLSX when analysts need a workbook. Download JSON for replay, Markdown or TXT for text archives, and PDF for a shareable report file. ## Match the Format to the Handoff Choose a format from the next consumer's exact requirements. Do not convert a file twice when Xquik already returns the required format. Use a live JSON page when code needs fresh tweets, followers, following rows, replies, profiles, or media fields. Keep the documented endpoint cursor with the same request filters. Use saved extraction JSON pages when a worker needs incremental processing. Append each completed page to JSON Lines through an idempotent sink. Use the extraction ID and requested `cursor` value as the page key. Persist the page and its `nextCursor` in one atomic commit. A retry must replace or skip an existing page key instead of appending duplicate tweets, followers, or replies. Use a JSON file when an application needs one structured download. The export contains the documented columns for that extraction tool. Store the extraction ID, tool type, and selected filters beside the file. Use CSV for CRM or spreadsheet imports. CSV exports include one header row. They also neutralize spreadsheet formula prefixes in result cells. Use XLSX when analysts need an Excel workbook. Use Markdown, PDF, or TXT when people need a readable review artifact instead of an import file. ## Pagination and Cursor Map Direct X API pages expose endpoint-specific cursor fields such as `has_next_page` and `next_cursor`. Keep the same query filters between requests and only change the cursor parameter documented on that endpoint. Extraction result pages use `hasMore` and `nextCursor`. Pass `nextCursor` back as `cursor`, and use `limit` up to `1000`; the default is `100`. File exports do not paginate. File exports are capped at 100,000 rows, and PDF exports are capped at 10,000 rows. Use extraction JSON pages or JSON Lines when you need to process larger jobs incrementally. For larger extractions, page results through `GET /api/v1/extractions/{id}`. Keep `cursor`, `hasMore`, and `nextCursor` in one checkpoint. Never continue from the downloaded file's last visible value. The file endpoint returns the first 100,000 rows ordered by result ID. PDF returns the first 10,000 rows. A successful file response does not prove the extraction contains no additional rows. ## Format Map Best for spreadsheet imports and CRM uploads. The response content type is `text/csv; charset=utf-8`. Best for replay, durable archives, and application handoffs. The file is a JSON array containing the extraction tool's documented columns. The response content type is `application/json; charset=utf-8`. Best for analysts who need a workbook. The response content type is `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`. Best for docs and text review. `md` returns a Markdown table. `md-document` returns numbered sections with labeled fields. Both return `text/markdown; charset=utf-8`. Best for a shareable report snapshot. The response content type is `application/pdf`. Best for plain text archives. The response content type is `text/plain; charset=utf-8`. ## Download and Validate an Export Use the server filename from `Content-Disposition`. `curl` can apply that filename without parsing the header yourself. ```bash theme={null} curl --fail --location --remote-header-name --remote-name \ "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=csv" \ -H "x-api-key: xq_YOUR_KEY_HERE" ``` Validate these properties before handing the file to another system: 1. Require HTTP `200`. 2. Match `Content-Type` to the requested format. 3. Store the `Content-Disposition` filename. 4. Reject an unexpectedly empty file. 5. Parse the selected format before marking the handoff complete. 6. Record the parsed row count and extraction ID. 7. Calculate a local checksum when an audit requires one. Do not assume a filename proves the format. Verify the response header and parser result. Never print CSV, JSON, XLSX, PDF, or TXT bytes to shared logs. ## Handoff Checkpoint Store enough context to resume a job, verify filters, and download the right format later. ```json theme={null} { "source": "xquik.response_formats", "extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "detail_path": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890?limit=1000", "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" }, "pagination": { "limit": 1000, "after": "990200", "hasMore": true, "nextCursor": "991200" }, "download": { "requested_format": "csv", "expected_content_type": "text/csv; charset=utf-8", "server_filename": "extraction-follower_explorer-2026-08-02.csv", "parsed_rows": 5000, "local_sha256": "calculated-after-download" }, "draw_export_path": "/api/v1/draws/f4bd00a2-7b4e-4e59-8e1b-72e2c9f12345/export?format=csv&type=winners", "next_action": "poll_until_completed_then_download" } ``` `local_sha256` is calculated by your workflow after download. Xquik does not return that field in the export response. ## Handle Export Failures * `400 invalid_format`: Use one of the 7 documented format values. * `401 unauthenticated`: Replace the missing or invalid credential. * `404 not_found`: Verify the extraction or draw ID belongs to the account. * `429 rate_limit_exceeded`: Wait for `Retry-After` before downloading again. For draw exports, also validate `type=winners` or `type=entries`. Do not retry an unchanged `400`, `401`, or `404` request. Retry `429` after the documented delay. ## Twitter API Export Questions ### How Do I Export Twitter API Results to CSV? Run an extraction for tweets, followers, following rows, replies, or profiles. After completion, request its export endpoint with `format=csv`. ### Can I Export Tweets as JSON? Yes. Use live JSON pages for incremental processing. Use `format=json` for one saved extraction file within the 100,000-row cap. ### Should I Use CSV or JSON for Twitter Results? Use CSV for CRM and spreadsheet imports. Use JSON for applications, replay, and structured result fields. Choose before building the downstream parser. ### When Should I Use XLSX Instead of CSV? Use XLSX when analysts need an Excel workbook. Use CSV when another system expects a simple delimited import. ### Can I Export More Than 100,000 Rows? Process larger extraction jobs through saved JSON pages. Append JSON Lines and resume with `cursor`. One file export stops at 100,000 rows. ### Why Does PDF Stop at 10,000 Rows? PDF uses the documented 10,000-row cap. Choose CSV, JSON, XLSX, Markdown, or TXT for file exports up to 100,000 rows. ### What Is the Difference Between `md` and `md-document`? `md` returns a Markdown table. `md-document` returns numbered result sections with labeled fields. Both use the Markdown response content type. ### Does This Download Twitter Images or Videos? No. This guide exports documented result fields. Use returned media URLs when your workflow separately processes tweet images or videos. ### Does This Export My Private X Account Archive? No. These endpoints export Xquik extraction or draw results. Use X's account archive process for your private account archive. ## Safety Checklist Do not print downloaded export bytes to shared logs. Store the `Content-Disposition` filename if your workflow needs stable file names. Validate `format` before making the request. Invalid formats return a 400 error. Store the job ID, tool type, filters, cursor, and chosen export path with the downstream task. ## Next Steps Build saved extraction jobs, poll results, and export finished rows. Download saved extraction rows as CSV, JSON, XLSX, Markdown, PDF, or TXT. Download giveaway winners or entries. Move follower data into CSV and CRM workflows. # Twitter Audience Discovery with Follower Exports Source: https://docs.xquik.com/guides/target-audience-discovery-workflow Find Twitter audience segments with user search, follower exports, following pages, verified followers, batch enrichment, and CSV or JSON handoff steps.
For the complete documentation index, see llms.txt.
Use this workflow when a sales, research, community, or marketing job starts with a topic, brand, creator, or competitor account and needs a scored audience list. Keep every row keyed by X user ID so exports, CRMs, agents, and warehouse loads can dedupe later. This workflow produces public profile and relationship evidence. It does not create an X Ads custom audience. It also does not return private emails, purchase behavior, age, or gender.
* Profiles: [Search users](/api-reference/x/search-users) · [Get user](/api-reference/x/twitter-profile-lookup) · [Batch users](/api-reference/x/batch-users) * Followers: [Followers](/api-reference/x/followers) · [Following](/api-reference/x/following) · [Verified followers](/api-reference/x/verified-followers) · [Followers you know](/api-reference/x/followers-you-know) · [Check follower](/api-reference/x/check-follower) * Lists: [List members](/api-reference/x/list-members) · [List followers](/api-reference/x/list-followers) * Communities: [Details](/api-reference/x/community-info) · [Members](/api-reference/x/community-members) · [Moderators](/api-reference/x/community-moderators) · [Timeline](/api-reference/x/community-tweets) · [Keyword search](/api-reference/x/community-search)
## Define the Audience Question Start with one decision your audience list must support. Examples include finding relevant creators, qualifying public profiles, or comparing follower communities. Keep the question beside every exported row. Choose a source that matches that decision: * User search finds public profiles matching a name, handle, or topic. * Followers show who follows a selected public account. * Following shows which accounts a selected profile follows. * Verified followers isolate returned profiles with X verification. * Tweet search confirms public conversation around a selected topic. Do not treat every follower as a qualified prospect. A follower relationship proves one public connection. It does not prove role, budget, intent, or permission to contact that person. ## Pick the Discovery Path Use `GET /api/v1/x/users/search?q={query}` or `people_search` to find candidate profiles by name, handle, or topic. Use `follower_explorer` or `GET /api/v1/x/users/{id}/followers` to collect people who already follow one seed account. Use `following_explorer` or `GET /api/v1/x/users/{id}/following` to collect accounts one seed profile follows. Use `verified_follower_explorer` or `GET /api/v1/x/users/{id}/verified-followers` when verified accounts should be scored separately. ## Seed Accounts Start with a query when you do not already have exact handles. Store the search query, rank, user ID, username, follower count, verification state, and cursor. ```bash theme={null} curl "https://xquik.com/api/v1/x/users/search?q=ai%20founder" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` If the seed list comes from another system, enrich up to 100 numeric user IDs per request before expanding the graph. ```bash theme={null} curl "https://xquik.com/api/v1/x/users/batch?ids=44196397,987654321" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Prefer numeric X user IDs as stable keys. Usernames and display names can change. Store the latest username as an attribute, not the CRM primary key. Keep `seed_query`, `seed_user_id`, `seed_username`, and `seed_rank` together. That evidence explains why each follower or following row entered the audience. ## Expand the Audience Use extraction jobs when the output needs estimate, retry, audit, and file download handoff. Use direct JSON pages when the app needs the freshest page now. `POST /api/v1/extractions/estimate`, then `POST /api/v1/extractions` with `toolType: "follower_explorer"`. Use `toolType: "following_explorer"` with `targetUsername` and optional `resultsLimit`. Use `toolType: "people_search"` with `searchQuery` for reusable profile search exports. Use `toolType: "verified_follower_explorer"` for a reusable verified follower export. ```bash theme={null} curl -X POST https://xquik.com/api/v1/extractions \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "toolType": "follower_explorer", "targetUsername": "username", "resultsLimit": 5000 }' | jq ``` Export the completed job when the audience list is ready for a CRM, sheet, or warehouse. ```bash theme={null} curl "https://xquik.com/api/v1/extractions/77777/export?format=csv" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -o target-audience.csv ``` ## Direct JSON Pages Use direct pages when the app owns the loop and checkpoint state. ```bash theme={null} curl "https://xquik.com/api/v1/x/users/username/followers?pageSize=200" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```bash theme={null} curl "https://xquik.com/api/v1/x/users/username/following?pageSize=200" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```bash theme={null} curl "https://xquik.com/api/v1/x/users/44196397/verified-followers" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Store `has_next_page` and `next_cursor` with the seed account and route. Pass `next_cursor` back as `cursor` only when `has_next_page` is true. Save a checkpoint after every completed page. Include the route, seed user ID, input cursor, next cursor, page size, and collection time. Retry from the last saved next cursor after a worker restart. Stop when `has_next_page` is false or `next_cursor` is empty. Also stop when a cursor repeats. Never share one cursor between followers and following routes. ## Combine Audience Sources Union sources when broad discovery matters. Intersect sources when stronger relationship evidence matters. Always join on numeric X user ID. Useful source combinations include: * Followers of several relevant creators reveal shared audience members. * Following lists reveal accounts your seed profiles actively chose. * User search plus followers validates a topic match and relationship. * Verified followers plus tweet search validates verification and activity. * Batch user enrichment refreshes profile fields before CRM upserts. Store one evidence row per source relationship. Build one normalized candidate row after collection. This preserves multiple reasons for the same candidate. ```json theme={null} { "candidate_user_id": "987654321", "source_count": 3, "sources": [ "followers:44196397", "followers:123456789", "search:ai founder" ], "matched_queries": ["ai founder"], "matching_tweet_ids": ["1893704267862470862"] } ``` ## Score Rows Create one normalized row per candidate before enrichment or outreach. ```json theme={null} { "audience_id": "ai-founder-q2", "seed_source": "GET /api/v1/x/users/search", "seed_query": "ai founder", "seed_user_id": "44196397", "candidate_user_id": "987654321", "candidate_username": "username", "display_name": "Xquik", "follower_count": 2400, "following_count": 430, "verified": true, "verified_type": "Business", "profile_image_url": "https://pbs.twimg.com/profile_images/xquik.jpg", "bio": "X automation platform", "location": "San Francisco", "source_route": "GET /api/v1/x/users/{id}/followers", "page_cursor": null, "matched_at": "2026-05-24T19:51:00.000Z" } ``` Score with fields Xquik already returns: follower count, following count, verification state, verification type, bio, location, profile image, account creation date, media count, website URL, and protected-account state. Use transparent, task-specific qualification rules. Store every rule result beside the final decision. Avoid one unexplained audience score. Count distinct seed accounts, source routes, and matched search queries. Evaluate returned bio, location, verification, and public profile counts. Keep matching tweet IDs, creation times, replies, reposts, and likes. Store `qualified`, `qualification_reasons`, and the ruleset version. Do not invent missing profile attributes. A blank location is unknown, not a failed location match. A missing verification type is not a personal account classification. A large follower count does not prove topical relevance. ## Validate Active Conversation Use tweet search when a candidate segment must be active around a topic before it enters a campaign, CRM list, or agent queue. ```bash theme={null} curl "https://xquik.com/api/v1/x/tweets/search?q=ai%20founder%20min_faves%3A10&verifiedOnly=true" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Store tweet IDs separately from audience rows. Do not overwrite the candidate profile row with the latest matching tweet. Use returned tweet fields for public conversation evidence. Store tweet text, author ID, creation time, reply count, repost count, like count, and quote count when returned. Keep the search query and cursor beside each page. Separate profile qualification from tweet qualification. A profile can match your audience even when no recent tweet matches. A matching tweet can also come from a profile that fails your audience rules. ## Build a Twitter Lead Generation Handoff Treat the exported file as a public-profile research list. Apply your consent, privacy, and outreach rules before contacting anyone. Xquik does not supply private email addresses or private messages through this workflow. Use numeric X user ID for CRM upserts. Keep these concrete columns: * `x_user_id` * `x_username` * `display_name` * `bio` * `location` * `follower_count` * `following_count` * `verified` * `verified_type` * `source_count` * `source_seed_ids` * `matched_queries` * `matching_tweet_ids` * `qualified` * `qualification_reasons` * `collected_at` * `ruleset_version` Keep public profile facts separate from your internal sales fields. Do not overwrite `bio`, `location`, or follower counts with CRM notes. Refresh public fields by X user ID and preserve prior collection timestamps. ## Measure Twitter Audience Insights Calculate insights only from collected public profiles and tweets. Report the sample size, collection time, seed accounts, query, and missing-field counts. Useful measurements include: * Shared followers across selected seed accounts * Followers versus following source counts * Verified and unverified profile counts * Returned profile locations without inferred geography * Bio terms found in returned descriptions * Recent matching authors and tweet counts * Reply, repost, like, and quote totals for matching tweets These measurements describe the collected sample. They do not represent every X user or private demographic attribute. Keep that limitation in dashboards, AI summaries, and client reports. ## Handle Audience Collection Failures * `400`: Correct the query, user ID, username, or page parameter. * `401`: Replace the missing or invalid credential. * `402`: Top up credits before resuming the saved cursor. * `404`: Verify the seed account or requested profile still resolves. * `424`: Retry the temporary read-service dependency failure. * `429`: Wait for `Retry-After` before requesting another page. * `502`: Retry the temporary read-service failure later. Retry `424`, `429`, and `502` with capped backoff. Do not restart from page one. Resume from the last committed cursor. Do not retry `400`, `401`, `402`, or `404` without changing the request or account state. ## Twitter Audience Discovery Questions ### How Do I Find My Target Audience on Twitter? Start with topic searches or relevant seed accounts. Expand their followers or following lists. Then qualify profiles with returned fields and matching tweets. ### How Do I Analyze a Competitor's Twitter Followers? Export that public account's followers. Keep the competitor's numeric X user ID as the seed. Dedupe candidates by their own numeric X user IDs. ### Can I Export Twitter Followers to CSV? Yes. Run a `follower_explorer` extraction, wait for completion, then export CSV. Use direct JSON pages when your application owns pagination and checkpoints. ### What Is the Difference Between Followers and Following? Followers chose to follow the seed account. Following lists accounts the seed profile chose. Keep these relationship directions separate in every row. ### Can Xquik Return Twitter Audience Demographics? No. This workflow returns documented public profile and tweet fields. Do not infer private age, gender, email, purchase behavior, or other demographics. ### How Do I Build a Twitter Lead List? Collect public profiles, preserve source evidence, and apply explicit qualification rules. Export only the fields your CRM needs. Apply consent and outreach requirements outside this collection workflow. ### How Do I Prevent Duplicate Audience Profiles? Use numeric X user ID as the unique key. Store usernames as mutable profile fields. Preserve every source relationship in separate evidence rows. ### How Do I Keep Audience Exports Current? Refresh profile fields by numeric X user ID. Keep `collected_at` on every snapshot. Never overwrite historical evidence without retaining its timestamp. ## Cost and Retry Notes Estimate extraction jobs before running large follower, following, verified follower, or people search exports. Direct JSON pages are metered by returned user or tweet rows. Low credit balances can return smaller pages or `402 insufficient_credits`. Treat cursors as opaque route checkpoints. They are not stable profile IDs. ## Next Steps Find seed profiles by topic, name, or handle. Build CSV, JSON, or XLSX follower files for CRM and warehouse handoff. Page accounts followed by one seed profile. Enrich up to 100 numeric user IDs in one request. # Twitter Trends by Region & WOEID Guide | Trends API Source: https://docs.xquik.com/guides/trends Find ranked X trends by WOEID region, preserve each trend query and rank, then search matching tweets with cursor pagination. Includes exact API steps.
For the complete documentation index, see llms.txt.
Xquik returns ranked X trends for 12 supported WOEID regions. Use each trend's `query` with [Search Tweets](/api-reference/x/search-tweets) to collect matching posts. Use this Twitter trends guide for regional topic discovery and monitoring. Each call returns one current snapshot. Store snapshots to build trend history. ## Choose the Trends Endpoint Use `GET /api/v1/trends` for the top-level Twitter Trends API response. It returns `trends`, `total`, and `woeid`. Use `GET /api/v1/x/trends` for the equivalent X API route. It returns `trends`, `count`, and `woeid`. Keep one response shape throughout each client. This prevents `total` and `count` from becoming competing fields in stored snapshots. ## Regions Use these WOEIDs in `woeid` for `GET /trends` or `GET /x/trends`. Omit `woeid` or pass `1` for worldwide trends. * `1` - Worldwide * `23424977` - United States * `23424775` - Canada * `23424900` - Mexico * `23424768` - Brazil * `23424975` - United Kingdom * `23424969` - Turkey * `23424950` - Spain * `23424829` - Germany * `23424819` - France * `23424856` - Japan * `23424848` - India ## Read a Regional Trends Snapshot When you call `GET /api/v1/trends`, Xquik fetches the latest trending topics for the requested WOEID. Results are cached briefly to keep responses fast. Each trend includes a `name`, optional `description`, optional `rank`, and optional `query` string. Rich responses can also include `promotedContent`, `tweetVolume`, and `url`. Use `name` as the visible topic or hashtag. Use `rank` for regional ordering. Use `query` for a follow-up tweet search. Fall back to `name` when `query` is missing. Treat `tweetVolume` as an optional estimate. Preserve `null` and missing values. Never replace them with zero. A missing estimate does not mean nobody posted about the topic. **Response:** ```json theme={null} { "woeid": 23424977, "total": 30, "trends": [ { "name": "#AI", "description": "Artificial Intelligence discussions", "rank": 1, "query": "#AI" } ] } ``` The `count` query parameter controls how many trends to return. Defaults to `30`; valid values are `1` through `50`. ## Compare Twitter Topic Trends Over Time The trends endpoints return current snapshots. They do not return stored history. Create history by saving each regional response on your schedule. Store these fields for every snapshot: * `captured_at`: your UTC collection timestamp * `woeid`: the requested region * `name`: the visible topic or hashtag * `query`: the recommended tweet-search expression * `rank`: the current regional position * `description`: the optional topic context * `tweetVolume`: the optional public-post estimate * `promotedContent`: the optional promotion identifier Compare normalized `name` values within the same WOEID. Track `first_seen`, `last_seen`, `current_rank`, `previous_rank`, and `best_rank`. Keep every snapshot immutable. Derived movement can change when late jobs arrive. An absent topic only left the requested result slice. It may still appear below your selected `count`. Increase `count` before treating absence as a meaningful change. ## Build a Multi-Region Twitter Trends Monitor Choose only the regions that match your market. Poll each WOEID independently. Store the WOEID beside every trend row. Never compare ranks across regions as if they share one list. Use `woeid=1` for a worldwide snapshot. Use country WOEIDs for regional comparisons. Xquik supports the 12 WOEIDs listed above. Unsupported country or city identifiers return `400 invalid_input`. A practical monitor follows this sequence: 1. Request the same `count` for each chosen WOEID. 2. Save the raw regional snapshot before enrichment. 3. Compare each topic with its previous regional rank. 4. Filter topics against your campaign or research scope. 5. Search tweets only for relevant topic queries. 6. Store matching tweets with the originating WOEID and rank. 7. Alert only after your movement or relevance rule passes. This sequence keeps unrelated trends out of tweet searches. It also preserves why every topic entered a dashboard, alert, or AI summary. ## Search Tweets Behind a Trend Fetch the top trend for a region, then search for tweets about it: ```bash cURL theme={null} # 1. Get top trend TREND=$(curl -s "https://xquik.com/api/v1/trends?woeid=23424977&count=1" \ -H "x-api-key: xq_YOUR_KEY_HERE" \ | jq -r '.trends[0].query') # 2. Search tweets about it curl -G "https://xquik.com/api/v1/x/tweets/search" \ --data-urlencode "q=$TREND" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ```javascript Node.js theme={null} const API_KEY = "xq_YOUR_KEY_HERE"; const headers = { "x-api-key": API_KEY }; // 1. Get top trend const trendsRes = await fetch("https://xquik.com/api/v1/trends?woeid=23424977&count=1", { headers }); const trendsData = await trendsRes.json(); const query = trendsData.trends[0].query; // 2. Search tweets about it const searchRes = await fetch(`https://xquik.com/api/v1/x/tweets/search?q=${encodeURIComponent(query)}`, { headers }); const searchData = await searchRes.json(); console.log(searchData); ``` ```python Python theme={null} import requests API_KEY = "xq_YOUR_KEY_HERE" headers = {"x-api-key": API_KEY} # 1. Get top trend trends_res = requests.get( "https://xquik.com/api/v1/trends", params={"woeid": 23424977, "count": 1}, headers=headers, ) query = trends_res.json()["trends"][0]["query"] # 2. Search tweets about it search_res = requests.get( "https://xquik.com/api/v1/x/tweets/search", params={"q": query}, headers=headers, ) print(search_res.json()) ``` ```go Go theme={null} package main import ( "encoding/json" "fmt" "io" "log" "net/http" "net/url" ) const apiKey = "xq_YOUR_KEY_HERE" func main() { // 1. Get top trend req, err := http.NewRequest("GET", "https://xquik.com/api/v1/trends?woeid=23424977&count=1", nil) if err != nil { log.Fatal(err) } req.Header.Set("x-api-key", apiKey) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } var trends struct { Trends []struct { Query string `json:"query"` } `json:"trends"` } json.Unmarshal(body, &trends) // 2. Search tweets about it searchURL := "https://xquik.com/api/v1/x/tweets/search?q=" + url.QueryEscape(trends.Trends[0].Query) req2, err := http.NewRequest("GET", searchURL, nil) if err != nil { log.Fatal(err) } req2.Header.Set("x-api-key", apiKey) resp2, err := http.DefaultClient.Do(req2) if err != nil { log.Fatal(err) } defer resp2.Body.Close() body2, err := io.ReadAll(resp2.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body2)) } ``` The trend response supplies a raw search expression such as `#AI`. `--data-urlencode` and `URLSearchParams` encode that expression once. Do not manually encode it before using these examples. Search results provide the tweets behind a selected topic. Preserve each tweet's ID, author, text, metrics, and creation time. Store the originating trend name, WOEID, rank, and collection timestamp beside those tweets. Use cursor pagination when you need more than one search page. Stop when the response has no next cursor. Also stop when a cursor repeats. ## Handle Trend Request Failures Handle each documented status before scheduling another regional request. * `400`: Use one of the 12 supported WOEIDs. * `401`: Replace the missing or invalid credential. * `402`: Top up credits or use an eligible paid read. * `424`: Retry the temporary read-service dependency failure. * `429`: Wait for `Retry-After` before retrying. * `502`: Retry the temporary read-service failure later. Use capped exponential backoff for `424`, `429`, and `502` responses. Do not retry `400`, `401`, or `402` without changing the request or account state. ## Twitter Trends API Questions ### What Is a WOEID in the Twitter Trends API? WOEID means Where On Earth ID. It selects one supported geographic trend list. Use `1` for Worldwide. Use a listed country code for regional trends. ### How Do I Get Twitter Trends by Country? Call either trends endpoint with that country's supported WOEID. Save the returned WOEID with every rank. This prevents regional rows from mixing. ### Can I Get Historical Twitter Trends? The endpoint returns one current snapshot. Poll it and store timestamped responses to build history. Keep the requested `count` with each snapshot. ### How Do I Find Tweets Behind a Trending Topic? Pass the trend's `query` to [Search Tweets](/api-reference/x/search-tweets). Use `name` only when `query` is missing. Paginate the matching tweet results. ### Does Every Trend Include a Public-Post Estimate? No. `tweetVolume` can be missing or `null`. Preserve that state in storage. Use rank movement as a separate regional signal. ### Can I Request City-Level Twitter Trends? No. Xquik currently accepts Worldwide and 11 listed country WOEIDs. An unsupported city WOEID returns `400 invalid_input`. ### How Many Trending Topics Can I Request? Request `1` through `50` topics. The default is `30`. ### Which Endpoint Should a New Client Use? Choose the response shape your client already uses. `/trends` returns `total`. `/x/trends` returns `count`. Both return ranked trends for one WOEID. ### Is Xquik the Official X Trends API? No. Xquik is an independent third-party service. It is not affiliated with X Corp. This guide documents Xquik endpoints and response fields. ## Next Steps Full endpoint reference with query parameters and response schema. Subscription pricing, credits, and per-operation costs. # X API Troubleshooting for Tweets, Exports & Webhooks Source: https://docs.xquik.com/guides/troubleshooting Fix API key, credit, rate-limit, cursor, tweet lookup, follower export, write-action, extraction, monitor, and webhook delivery errors. Follow exact steps.
For the complete documentation index, see llms.txt.
Common issues, error codes, and solutions. If your problem isn't covered here, contact [support@xquik.com](mailto:support@xquik.com). ## Error codes ### 401 Unauthenticated The API key or OAuth token is missing, invalid, expired, or revoked. Check the following: * **API key header:** Send `x-api-key: xq_...` or `Authorization: Bearer xq_...`. * **OAuth header:** Send `Authorization: Bearer `. OAuth clients normally manage this automatically. * **Key format:** Must start with `xq_`. If it doesn't, you're using the wrong value. * **Key revoked:** Revoked keys return 401 immediately. Generate a new key from the dashboard. * **OAuth token expired:** Let the MCP client refresh the token. Reconnect Xquik if refresh fails. * **API key management auth:** Listing, creating, and revoking keys require a same-origin dashboard session. API keys and OAuth bearer tokens cannot manage keys. ```bash theme={null} # Correct curl https://xquik.com/api/v1/account \ -H "x-api-key: xq_your_api_key_here" # Wrong - missing header curl https://xquik.com/api/v1/account ``` ### 401 authentication or 402 payment required Anonymous non-MPP paid reads return `401` with a Bearer challenge and guest wallet action. Direct MPP reads return `402` with a Payment challenge and the same action. Account or guest credit failures also return `402`. The failed request creates no checkout. Solutions: * For account keys, check `GET /api/v1/account` and use only the advertised account payment action * For guest keys, check [guest wallet status](/api-reference/guest-wallets/status) and use only the advertised guest top-up action * For the 7 direct MPP operations, complete the MPP challenge or ask the user to confirm a guest wallet amount * For the other 26 guest-eligible reads, authenticate or ask the user to confirm a guest wallet amount * Use [Estimate Extraction](/api-reference/extractions/twitter-scraping-cost-estimator) before running extractions to avoid surprises Ask the user to choose an option and amount before creating checkout. Never trigger checkout, top-up, or subscription automatically after `401` or `402`. ### Monitor Credits Monitor slots are unlimited. Active monitors cost 21 credits per hour. They require available credits while enabled. ```json theme={null} { "error": "insufficient_credits", "message": "Insufficient credits" } ``` Solutions: * Pause an unused account monitor with `PATCH /api/v1/monitors/{id}` and `{ "isActive": false }` * Pause an unused keyword monitor with `PATCH /api/v1/monitors/keywords/{id}` and `{ "isActive": false }` * Delete an unused monitor with `DELETE /api/v1/monitors/{id}` or `DELETE /api/v1/monitors/keywords/{id}` * Check current monitor billing: `GET /api/v1/account` shows `monitorsUsed` and `monitorBilling` ### 429 Too Many Requests You've exceeded the API rate limit. The API uses fixed windows per tier: `GET`, `HEAD`, and `OPTIONS` share 300 requests per 1 second. `POST`, `PUT`, and `PATCH` share 120 requests per 60 seconds. `DELETE` requests are limited to 60 requests per 60 seconds. Solutions: * Respect `Retry-After`; otherwise start at 1 second, add jitter, and stop after 3 retries. * Requests sent before the fixed window resets keep returning `429` until `Retry-After` elapses. * Check the `Retry-After` header for the server-recommended wait time. * Use webhooks instead of polling. They push detected events without repeated event-list calls. * Batch your logic. Fetch events once per minute instead of once per second. See the [Rate Limits](/guides/rate-limits) guide for backoff code examples. ### 502/503 Read Service Busy or Unavailable The read service is temporarily unavailable or busy. This is usually transient. Solutions: * Respect `Retry-After` when present, then retry the request * If no `Retry-After` header is present, retry after 5-10 seconds * Use exponential backoff (see [Error Handling](/guides/error-handling)) * If the error persists for more than 5 minutes, the read service may be experiencing an outage The same applies to draws and tweet, profile, follower, reply, timeline, community, and list endpoints under `/api/v1/x/*`. ## Common questions ### Webhooks not arriving? Webhooks can fail silently. Walk through this checklist: 1. **Webhook is active:** Verify `isActive: true` and `deliveryStatus: "active"` via `GET /api/v1/webhooks`. Paused webhooks do not receive deliveries. 2. **HTTPS required:** HTTP endpoints are rejected. Your URL must start with `https://`. 3. **Response time:** We recommend responding with `2xx` within 10 seconds. Slow responses may be treated as failures. 4. **Check deliveries:** Call `GET /api/v1/webhooks/{id}/deliveries` to see delivery status, attempt count, and error messages. 5. **Correlate source events:** Call `GET /api/v1/events/{id}` with the stored `streamEventId`. 6. **Local testing:** If using ngrok or a tunnel, verify it's running and the URL is current. Ngrok URLs change on restart (free plan). 7. **Needs attention:** If `deliveryStatus` is `needs_attention`, fix the receiver and call `POST /api/v1/webhooks/{id}/resume`. The receiver must pass a signed test before delivery resumes. 8. **Event type mismatch:** Your webhook must subscribe to the event types your monitors produce. A webhook listening for `tweet.new` won't receive `tweet.reply` events. > **Tip:** See the [Webhook Testing](/guides/twitter-webhook-testing) guide for a step-by-step local setup with ngrok. ### Monitor not tracking events? If your monitor is active but no events appear: * **Check `isActive`:** Confirm via `GET /api/v1/monitors/{id}` that `isActive` is `true`. Paused monitors don't track. * **Event propagation delay:** Events take seconds to minutes to appear depending on X API latency. This is normal. * **Event types:** Verify your monitor's `eventTypes` array includes the type you expect. A monitor tracking only `["tweet.new"]` won't capture replies or retweets. * **Account activity:** The monitored X account must actually post content matching your event types. No posts = no events. * **Pagination:** If listing events, check `hasMore` in the response. Older events may be on subsequent pages. ### How do I replay stored monitor events? Call `GET /api/v1/events?monitorId={id}&limit=50` for account monitors, or `GET /api/v1/events?keywordMonitorId={id}&limit=50` for keyword monitors, then process each event once. If `hasMore` is `true`, store `nextCursor` and pass it as `cursor` on the next request. Add `eventType` when you need to separate follows, tweets, replies, or keyword matches. ### Write action still pending? Inspect the durable action returned by the write. * Store `id`, `request.hash`, `account`, `target`, `billing`, and `statusUrl` * Poll `statusUrl` while `terminal` is `false` * Respect `Retry-After`, `pollAfterMs`, and `nextAction` * Retry only when `safeToRetry` is `true`, using a new `Idempotency-Key` * Verify the result before retrying when `nextAction.type` is `verify_result` ### How do I check my usage? Call `GET /api/v1/account`. The `creditInfo` object shows your balance: ```json theme={null} { "creditInfo": { "balance": "42500", "lifetimePurchased": "140000", "lifetimeUsed": "97500", "autoTopupEnabled": false } } ``` * `creditInfo.balance`: Remaining credits available for metered calls * When `balance` reaches `0`, metered calls are rejected until credits are topped up or auto top-up triggers The dashboard also displays usage graphically on the billing page. See [Billing & Usage](/guides/billing) for credit costs and billing. ### Can I use the API without a subscription? Yes. Full account metered operations work while enough available credits remain. Choose the access boundary: * **Guest wallet:** Prepay 33 eligible GET routes through a confirmed $10-$250 USD hosted checkout. * **MPP:** Pay 7 fixed-price GET operations per request without an account or API key. * **Full account:** Use available account credits for writes, monitors, extractions, draws, and connected-account reads. Webhooks and account management are free. Subscribe for monthly credits or top up from the [dashboard billing page](https://dashboard.xquik.com/en/account?tab=subscription). Remaining credits stay usable after a plan ends. ### How do I connect an AI agent? Xquik has 2 MCP servers. Choose based on what the agent needs to do. Connect `https://docs.xquik.com/mcp`. It is read-only and requires no auth. Connect `https://xquik.com/mcp`. Full credentials expose 120 catalog routes. Of these, 119 return JSON or text. Guest `paid_reads` keys expose 33 GET routes. Setup: 1. For docs search, add `https://docs.xquik.com/mcp`. 2. For account actions, use a full API key or OAuth login. Prefer OAuth when the client supports browser authorization. 3. For guest reads, activate a guest key through direct REST, then authenticate MCP with that key. Guest wallet creation, status, and top-up are never executable through MCP. Current client paths are: * **OAuth 2.1:** Claude.ai, Claude Desktop, Claude Code, ChatGPT, Cursor, VS Code, Windsurf, OpenCode, Gemini CLI, GitHub Copilot CLI, Cline, and Qwen Code * **API-key fallback:** affected Codex and Goose releases until their callback implementations preserve the RFC 9207 issuer value * **API-key only:** Roo Code's archived final release has no MCP OAuth provider * **No native MCP:** Pi requires a separately installed and tested adapter See [Docs MCP server](/mcp/docs-mcp) for docs search, [MCP Server overview](/mcp/overview) for account actions, and the [MCP Tools](/mcp/tools) reference for tool details. ### MCP OAuth does not open or shows an unknown application Use this checklist: 1. Enter the exact server URL: `https://xquik.com/mcp`. 2. Remove any manually entered client ID or client secret unless your client requires preregistration. 3. Remove and re-add the connector to restart OAuth discovery. 4. Start login from the MCP client. Do not open `/api/auth/google` directly. 5. Allow the browser to return to the client's exact callback URL. Xquik publishes all required discovery documents: * Protected resource metadata: `https://xquik.com/.well-known/oauth-protected-resource/mcp` * Authorization server metadata: `https://xquik.com/.well-known/oauth-authorization-server` * Agent-readable auth guide: `https://xquik.com/auth.md` Xquik supports CIMD and DCR. Let the client use its documented registration flow. Claude selects CIMD when the authorization metadata advertises support and the public `none` authentication method; otherwise it can use DCR. ChatGPT app creators choose CIMD or DCR during setup. If a URL-form `client_id` fails, its public HTTPS metadata URL must include an explicit path. A trailing `/` is sufficient. It must return JSON without a redirect. Repeat the exact URL in `client_id`. List the callback in `redirect_uris`. ### Codex OAuth issuer validation error This workaround applies to affected Codex and Goose releases. They can stop after browser approval and report: ```text theme={null} Authorization server response missing required issuer: expected https://xquik.com ``` Affected releases discard the RFC 9207 `iss` authorization response value before token exchange. Xquik already returns `iss=https://xquik.com` and advertises `authorization_response_iss_parameter_supported: true`. Xquik keeps issuer validation enabled. Codex users can track the [upstream Codex issue](https://github.com/openai/codex/issues/31573) for the fixed release. Retrying OAuth does not restore the discarded value. Export an API key before configuring either fallback: ```bash theme={null} export XQUIK_API_KEY="xq_your_api_key_here" ``` Add the API MCP server to `~/.codex/config.toml` or a trusted project's `.codex/config.toml`: ```toml theme={null} [mcp_servers.xquik] url = "https://xquik.com/mcp" bearer_token_env_var = "XQUIK_API_KEY" ``` Restart Codex, then run `codex mcp list`. Do not run `codex mcp login xquik` while the bearer-token fallback is active. Never commit the key or place its value directly in `config.toml`. For Goose, add this extension to `~/.config/goose/config.yaml`: ```yaml theme={null} extensions: xquik: type: streamable_http name: xquik enabled: true uri: "https://xquik.com/mcp" headers: Authorization: "Bearer ${XQUIK_API_KEY}" env_keys: - XQUIK_API_KEY envs: {} ``` Goose substitutes `XQUIK_API_KEY` before sending the header. Remove the custom header configuration only after your Goose release preserves the authorization response issuer. The Docs MCP server needs no authentication, so Codex and Goose can retrieve these instructions while API MCP OAuth is blocked: ```bash theme={null} codex mcp add xquik-docs --url https://docs.xquik.com/mcp ``` Goose can connect to the same documentation server without OAuth: ```bash theme={null} goose session --with-streamable-http-extension https://docs.xquik.com/mcp ``` When the [upstream Codex issue](https://github.com/openai/codex/issues/31573) identifies a fixed release, upgrade Codex, remove `bearer_token_env_var`, and run `codex mcp login xquik` again. ### How do I export extraction results? Call `GET /api/v1/extractions/{id}/export?format=csv` (or `xlsx` or `md`). The response is a file download. Limits: * Maximum 100,000 rows per export (10,000 for PDF) * Available formats: CSV, JSON, Markdown, Markdown Document, PDF, TXT, XLSX See [Export Extraction](/api-reference/extractions/export) for column details and code examples. ## Still stuck? * [Authentication](/api-reference/authentication): API key format, header requirements, and dual auth details. * [Error Handling](/guides/error-handling): Error codes, retry strategies, and graceful degradation. * [Billing & Usage](/guides/billing): Pricing, credits, and per-operation costs. # Tweet Metadata, Profile & Media API Field Guide Source: https://docs.xquik.com/guides/tweet-profile-api-fields Map Twitter API fields for tweets, profiles, media, replies, engagement metrics, IDs, cursors, exports, REST, SDK, MCP, and Actor responses with JSON examples.
For the complete documentation index, see llms.txt.
Use this guide to get tweet metadata from Xquik responses. It maps Twitter API fields for tweets, user profiles, media files, and replies. Compare Twitter API user fields, Twitter profile fields, and media objects. Review tweet engagement metrics, quote context, repost context, and pagination. Quick answer: treat IDs as strings. Treat optional fields as conditional. Keep reply, quote, and repost relationships separate. Store a collection timestamp beside mutable engagement and profile counts. This Twitter metadata guide focuses on public fields X supplies. Xquik preserves every documented public field supplied for supported reads. Core fields remain stable. Optional fields appear only when X supplies them.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
The [API reference](/api-reference/overview) defines exact REST and SDK types. Each API response must follow those documented types. The Twitter API response fields below match that public contract. The MCP contract normalizes names for agent use. Most fields use `snake_case`. REST `createdAt` becomes MCP `created`. It does not become `created_at`. ## Choose the Object Before Mapping Fields | Need | Primary object | Stable join key | | ---------------------------- | ----------------- | ---------------------------------- | | Tweet text and engagement | Tweet | `id` | | Reply or thread relationship | Tweet | `conversationId` and `inReplyToId` | | Quote context | `quoted_tweet` | Nested tweet `id` | | Repost context | `retweeted_tweet` | Nested tweet `id` | | Author profile | `author` | Author `id` | | Image, video, or GIF | `media[]` | `mediaKey` or media `id` | | Pagination | Page envelope | Returned cursor | Do not flatten every object into one unversioned row. Nested tweets and authors have their own IDs. Store those IDs before building joins. ## Field Presence Rules Tweet objects require `id`, `text`, and documented engagement counts. Profile objects require `id`, `username`, and `name`. Optional fields are omitted when X does not supply them. Absence is not an empty string, false value, or zero count. Keep tweet, user, media, conversation, and reply IDs as strings. Large IDs can lose precision in spreadsheet or JavaScript number types. Likes, replies, reposts, quotes, views, bookmarks, followers, and following counts can change after collection. Store `collected_at` downstream. A zero tweet metric can mean X did not report that count. Do not treat every zero as a measured absence. ## Tweet Fields Every tweet includes its ID, text, metrics, and available author profile. These optional fields preserve additional X metadata. Core tweet fields group into these roles: * Identity: `id`, `type`, `url`, `createdAt`, and `lang` * Publishing client: `source` * Text: `text`, `displayTextRange`, `entities`, and `contentDisclosure` * Reply state: `isReply`, `isLimitedReply`, and `conversationId` * Reply targets: `inReplyToId`, `inReplyToUserId`, and `inReplyToUsername` * Quote state: `isQuoteStatus` and `quoted_tweet` * Repost context: `retweeted_tweet` * Long-form context: `isNoteTweet` * Related objects: `author` and `media` * Counts: `retweetCount`, `replyCount`, `likeCount`, and `quoteCount` * Reach: `viewCount` and `bookmarkCount` | Field | Description | | ------------------- | -------------------------------- | | `article` | X Article metadata | | `card` | Link card metadata | | `communityNote` | Community Note metadata | | `edit` | Edit history and remaining state | | `isTranslatable` | Translation availability | | `noteTweet` | Long-form post metadata | | `place` | Tagged place metadata | | `possiblySensitive` | X sensitivity state | | `previousCounts` | Pre-edit engagement counts | | `viewState` | X view-state metadata | Tweets also include available entities, disclosures, nested tweets, and media. ### Tweet Metadata Example ```json theme={null} { "id": "1893704267862470862", "text": "A public tweet with a reply and media.", "createdAt": "2026-05-24T20:32:58.000Z", "url": "https://x.com/example/status/1893704267862470862", "lang": "en", "isReply": true, "isNoteTweet": false, "isQuoteStatus": false, "conversationId": "1893600000000000000", "inReplyToId": "1893690000000000000", "inReplyToUserId": "987654321", "inReplyToUsername": "parent_author", "retweetCount": 8, "replyCount": 3, "likeCount": 42, "quoteCount": 2, "viewCount": 1500, "bookmarkCount": 4, "author": { "id": "123456789", "username": "example", "name": "Example Account" }, "media": [ { "mediaUrl": "https://pbs.twimg.com/media/example.jpg", "type": "photo", "url": "https://x.com/example/status/1893704267862470862/photo/1", "altText": "Product dashboard with a tweet search result" } ] } ``` The example illustrates field placement. Read the endpoint response for actual values. Never copy example IDs into a production join. ### Map Replies, Quotes, and Reposts Use `isReply` to identify a reply. Use `inReplyToId` for its direct parent. Use `conversationId` for the root conversation. These IDs can differ. Use `isQuoteStatus` to identify quote context. Read `quoted_tweet` for the nested quoted tweet when available. Keep the quoting tweet as its own row. Read `retweeted_tweet` for original repost context. Keep the returned outer tweet ID and nested tweet ID separate. This prevents incorrect engagement joins. Keep original tweets separate from outer repost records. Join `inReplyToId` to the parent tweet. Keep `conversationId` for the full thread. Join `quoted_tweet.id` to the quoted tweet. Attribute quote text to the outer tweet. Join `retweeted_tweet.id` to the original tweet. Do not merge their metric snapshots. Check `isNoteTweet` and `noteTweet`. Preserve long-form text and rich-text metadata when present. ### Interpret Tweet Engagement Fields `likeCount`, `replyCount`, `retweetCount`, `quoteCount`, `viewCount`, and `bookmarkCount` are count snapshots. They can change after collection. Store `tweet_id`, every count, and `collected_at` in one snapshot row. Compare snapshots with the same tweet ID. Do not overwrite history when measuring growth. `previousCounts` can preserve pre-edit engagement counts. `edit` can describe edit history. Keep both objects when analyzing edited tweets. ### Map Entities and Content Labels Read `entities` for URLs, mentions, hashtags, and other parsed text markers. Read `displayTextRange` before slicing rendered text. Preserve the original tweet text beside any normalized tokens. Read `contentDisclosure` for documented content labels. Read `possiblySensitive` for the returned sensitivity state. Never infer either field when it is absent. ## Profile Fields Every profile includes `id`, `username`, and `name`. Counts, verification, images, bios, and other metadata appear when X supplies them. Core profile fields group into these roles: * Identity: `id`, `username`, `name`, and `createdAt` * Bio: `description`, `profile_bio`, `location`, and `url` * Audience: `followers` and `following` * Activity: `statusesCount`, `mediaCount`, and `favouritesCount` * Verification: `verified`, `isVerified`, `isBlueVerified`, and `verifiedType` * Images: `profilePicture`, `coverPicture`, and `profileBannerUrl` * Access: `protected`, `unavailable`, and `unavailableReason` * Safety: `possiblySensitive` and `withheldInCountries` * Automation: `isAutomated` and `automatedBy` * Features: `hasCustomTimelines`, `isTranslator`, and `communityRole` * Pinned content: `pinnedTweetIds` Treat `author` as the user object for each returned tweet. Store the user profile using its stable `id`. A profile page can change its username, name, bio, and images. Keep pinned tweet IDs as strings. | Field | Description | | --------------------------------- | -------------------------------- | | `affiliatesHighlightedLabel` | Affiliate label metadata | | `businessAccountAffiliatesCount` | Business affiliate count | | `creatorSubscriptionsCount` | Creator subscription count | | `hasGraduatedAccess` | Graduated access state | | `hasHiddenSubscriptionsOnProfile` | Hidden subscription state | | `highlightsInfo` | Profile highlights metadata | | `identityVerification` | Identity verification metadata | | `isProfileTranslatable` | Profile translation availability | | `parodyCommentaryFanLabel` | Parody or fan label | | `profileDescriptionLanguage` | Detected bio language | | `profileImageShape` | Profile image shape | | `profileInterstitialType` | Profile interstitial type | | `profileSortEnabled` | Profile sorting state | | `profileTranslatorType` | Profile translator type | | `superFollowEligible` | Subscription eligibility | Xquik removes account-specific actions, permissions, and relationships from public reads. Use dedicated write routes or X for account state. ### Twitter Profile API Example ```json theme={null} { "id": "9876543210", "username": "example_user", "name": "Example User", "description": "Developer building tweet monitoring tools.", "followers": 12500, "following": 420, "statusesCount": 8600, "mediaCount": 730, "verified": true, "isBlueVerified": false, "isVerified": true, "verifiedType": "Business", "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg", "coverPicture": "https://pbs.twimg.com/profile_banners/example.jpg", "location": "London", "createdAt": "2018-04-12T10:30:00.000Z", "protected": false, "pinnedTweetIds": ["1893704267862470862"] } ``` Use `id` as the profile join key. Usernames can change. Display names are not unique. Keep the observed username and collection time beside each snapshot. ### Interpret Profile Counts Use `followers` for the returned follower count. Use `following` for accounts the profile follows. Use `statusesCount` for the returned post count. Use `mediaCount` for posts containing media. Do not calculate follower growth from one response. Store dated profile snapshots. Compare the same X user ID across collection times. ### Keep Verification Fields Separate Preserve `verified`, `isVerified`, `isBlueVerified`, and `verifiedType` as distinct fields. X can return different verification signals. Do not collapse them into one custom boolean. `identityVerification` and `affiliatesHighlightedLabel` provide separate profile metadata. Preserve those objects when present. ### Handle Protected or Unavailable Profiles Use `protected` for the returned privacy state. Use `unavailable` and `unavailableReason` for unavailable profiles. Do not replace omitted profile fields with invented values. Keep `withheldInCountries` and `possiblySensitive` when returned. These fields describe public response state. They do not grant access to hidden content. ## Media Fields Media rows keep their URL and type. These optional fields add detail. The Twitter media API field section covers returned photos, videos, and GIFs. Store media files only when returned URLs and availability allow it. Every media object includes `mediaUrl`, `type`, and `url`. | Field | Description | | -------------------- | ---------------------------- | | `allowDownload` | Download permission | | `altText` | Accessibility text | | `aspectRatio` | Video aspect ratio | | `availabilityStatus` | Media availability state | | `displayUrl` | Display URL | | `durationMillis` | Video duration | | `expandedUrl` | Expanded media URL | | `faceRects` | Detected face rectangles | | `focusRects` | Crop focus rectangles | | `height` | Media height | | `id` | Media ID | | `indices` | Source-text indices | | `mediaKey` | X media key | | `monetizable` | Monetization state | | `sizes` | Available image sizes | | `videoVariants` | Video encodings and bitrates | | `width` | Media width | ### Map Photos, Videos, and GIFs Read `type` before selecting a media workflow. Supported values are `photo`, `video`, and `animated_gif`. Keep the original type with every media row. Use `mediaUrl` as the returned preview URL. Use `url` as the X media link. Use `expandedUrl` and `displayUrl` only when present. Preserve `altText` for accessibility. Preserve `width`, `height`, and `aspectRatio` for layout. Preserve `durationMillis` for video duration. `videoVariants` can contain multiple encodings and bitrates. Keep the complete array when another service selects playback quality. Do not invent a missing variant. Use `allowDownload` and `availabilityStatus` when returned. Check both before a media handoff. A media object does not guarantee permanent file availability. ### Store a Media Join Row ```json theme={null} { "tweet_id": "1893704267862470862", "media_id": "1912345678901234567", "media_key": "3_1912345678901234567", "media_type": "video", "media_url": "https://pbs.twimg.com/ext_tw_video_thumb/example.jpg", "x_media_url": "https://x.com/example/status/1893704267862470862/video/1", "duration_millis": 18400, "alt_text": "Short product demonstration", "collected_at": "2026-05-24T20:33:00.000Z" } ``` Create one row per media object. Join it back through `tweet_id`. Keep all variants in a child table or structured column. ## Reply Coverage Reply coverage depends on X. X can hide, rank, or omit counted replies. Omit `mode` for automatic maximum direct-reply coverage with pagination. Pass each `next_cursor` back unchanged as `cursor`. This keeps the standard response shape and billing. Use `GET /api/v1/x/tweets//replies?mode=complete&limit=25000`. Complete mode adds nested replies and detailed diagnostics. Direct replies match `inReplyToId` to the requested tweet. Keep `nested_replies` separate. Trust `diagnostic.complete` instead of row count. Complete mode requires 80% of X's current reported direct replies. HTTP 424 `replies_incomplete` returns the collected rows and coverage diagnostic. Inspect `coveragePercentage`, strategy results, cursor failures, and fallback. Disclose that reply coverage depends on X. ### Store Reply Relationships Keep these fields for each collected reply: * `id` for the reply tweet * `inReplyToId` for the direct parent * `conversationId` for the thread root * `author.id` for the reply author * `createdAt` for ordering * `nested_replies` for separately returned descendants Do not infer a direct parent from array position. Use `inReplyToId`. Keep `nested_replies` separate from direct replies when measuring first-level coverage. ## Twitter API Pagination Fields Tweet, profile, follower, reply, list, and timeline routes can return page envelopes. Keep each endpoint's documented field names. REST read pages commonly return `has_next_page` and `next_cursor`. Pass the returned cursor back as `cursor`. Stop when `has_next_page` is false. Extraction result pages return `hasMore` and `nextCursor`. Pass `nextCursor` back as `cursor`. Do not mix REST cursors with extraction cursors. Commit rows and their next cursor together. A retry must upsert by tweet ID, user ID, or media ID before advancing. ## Field Mapping Handoff ```json theme={null} { "schema": "xquik.tweet_profile_fields.v1", "source_route": "GET /api/v1/x/tweets/1893704267862470862", "tweet_key": "id", "profile_key": "author.id", "media_key": "media[].mediaKey", "reply_parent_key": "inReplyToId", "conversation_key": "conversationId", "quote_key": "quoted_tweet.id", "repost_key": "retweeted_tweet.id", "count_fields": [ "likeCount", "replyCount", "retweetCount", "quoteCount", "viewCount", "bookmarkCount" ], "ids_as_strings": true, "optional_field_policy": "preserve_absence", "collected_at": "2026-05-24T20:33:00.000Z" } ``` Use this checkpoint when building CSV, warehouse, search-index, or CRM rows. Keep nested JSON when flattening would erase relationships. ## Surface Mapping | Surface | Field style | Contract | | -------------- | --------------------- | ------------------------------------------------------------------------------ | | REST API | Mostly `camelCase` | `quoted_tweet`, `retweeted_tweet`, and `profile_bio` are documented exceptions | | Generated SDKs | Language-native names | Generated OpenAPI models | | MCP | Normalized | Most names use `snake_case`; REST `createdAt` becomes MCP `created` | | Apify Actors | Configurable | Dataset schemas and Actor READMEs | Xquik omits unavailable optional fields. It never invents missing X values. ### Keep Surface Names Explicit Do not assume one casing rule covers every field. REST uses mostly camel case. The documented nested exceptions keep their public names. Generated SDKs expose language-native models from OpenAPI. Use the generated property names for that SDK version. Do not hand-convert them from REST. MCP tools normalize most response names for agents. REST `createdAt` becomes MCP `created`. Actor datasets follow each Actor's documented output mode. ## Avoid Tweet Metadata Mapping Errors Precision lost. Store tweet, user, conversation, reply, and media IDs as strings before spreadsheet or JavaScript processing. Meaning changed. Preserve field absence. Add a separate downstream default only when the consumer requires one. Relationship lost. Keep `inReplyToId` for the parent. Keep `conversationId` for the root. Counts misattributed. Keep outer and nested tweet IDs in separate rows. History split. Use the stable X user ID. Store the observed username as an attribute. Mapping failed. Map REST `createdAt` and MCP `created` explicitly. ## Tweet Metadata and Profile API Questions ### How Do I Get Tweet Metadata? Call the route matching your tweet, timeline, profile, or reply task. Read the returned API response through its documented schema. Keep IDs, counts, relationships, and cursors without inventing missing fields. ### Which Twitter API Fields Should I Store? Start with IDs, text, timestamps, authors, engagement counts, media, and relationships. Add optional fields only when X supplies them. Store collection time beside mutable counts. ### Which Twitter API User Fields Identify a Profile? Use `id` as the stable profile key. Keep `username`, `name`, bio, images, verification, followers, and following. Record collection time beside changing profile counts. ### How Do I Read Twitter API Response Fields? Follow each Xquik route's response schema in the API reference. Use the field names documented for each surface. Apply only documented mappings between REST, SDK, MCP, and Actor names. ### How Do I Read Twitter API Pagination Fields? Twitter API pagination uses the response envelope documented for each route. Read `has_next_page` with `next_cursor` for REST pages. Read `hasMore` with `nextCursor` for extraction result pages. Never mix cursors between those response envelopes. ### What Metadata Does a Tweet Include? A tweet includes its ID, text, counts, URL, language, and relationship fields. It can also include author, media, entities, labels, and edit objects. Article, place, quote, repost, and Note Tweet objects can also appear. ### How Do I Find a Tweet ID? Read the response `id` field. Keep it as a string. Use it for lookups, joins, dedupe, exports, and engagement snapshots. ### What Is a Twitter Conversation ID? `conversationId` identifies the root tweet for the returned conversation. `inReplyToId` identifies one reply's direct parent. Preserve both fields. ### How Do I Know Whether a Tweet Is a Reply? Check `isReply`. Then read `inReplyToId`, `inReplyToUserId`, and `inReplyToUsername` when present. ### How Do Quotes Differ From Reposts? Quotes can add new outer tweet text and `quoted_tweet` context. Reposts expose original context through `retweeted_tweet`. Keep both tweet IDs separate. ### Which Fields Contain Twitter Engagement Metrics? Use `likeCount`, `replyCount`, `retweetCount`, `quoteCount`, `viewCount`, and `bookmarkCount`. Store collection time because these counts can change. ### Does the Twitter Profile API Include Follower Counts? Yes. Read `followers` and `following` when supplied. Store dated snapshots for growth tracking. Join snapshots through the profile `id`. ### Why Is a Tweet or Profile Field Missing? The field is optional and X did not supply it for that response. Preserve the absence. Never replace it with an invented value. ### How Do I Get Tweet Media Metadata? Read the tweet's `media` array. Use `type`, URLs, dimensions, alt text, duration, availability, and video variants when present. ### Can I Export Tweet Metadata to CSV? Yes. Flatten selected scalar fields and keep stable IDs. Store nested author, media, quote, and repost objects separately. Use linked rows or JSON columns. ### Do REST, SDK, MCP, and Actor Fields Match Exactly? They share the documented contract but can expose different field naming. Follow the OpenAPI model, MCP response shape, or Actor dataset schema directly. # Export Twitter Replies With a Scraper API Guide Source: https://docs.xquik.com/guides/tweet-replies-export Export Twitter replies through a scraper API. Estimate credits, paginate reply rows, and save CSV, JSON, or XLSX files with exact Xquik API steps and errors.
For the complete documentation index, see llms.txt.
Export Twitter replies as CSV, JSON, or XLSX rows. Use `reply_extractor` for saved files with a defined result cap. Scrape Twitter replies through the live API when an application needs current pages. Send reply authors, text, media, and engagement counts downstream. Route rows to moderators, CRMs, warehouses, queues, or agents. ## When to use this workflow Use `reply_extractor` plus CSV or XLSX export when analysts need reply rows. Use `reply_extractor` plus paginated JSON results for queues, CRMs, or warehouses. Use the direct replies API plus JSON Lines rows before storing, hiding, labeling, or routing replies. Use `GET /x/tweets/{id}/replies` when you only need the newest reply page. 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. User ID, username, display name, follower count, verified state, and profile image. Tweet ID, tweet text, and tweet created time. Likes, reposts, replies, quotes, views, and bookmarks. 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: ```json theme={null} { "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": "username", "conversation_id": "1893704267862470862", "export_format": "csv" }, "handoff_state": "poll_until_completed_then_export" } ``` Keep `estimatedResults`, `creditsRequired`, `creditsAvailable`, `allowed`, and `source` with `targetTweetId`; `source` is `replyCount` when the tweet count lookup succeeds. Store the returned job `id`, `status`, and `poll_path`; do not expect reply rows in the create response. Store `page_cursor`, `next_cursor`, and `has_more` for JSON page loops, then pass `nextCursor` back as `cursor`. 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. ```bash theme={null} 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`. A successful tweet count lookup sets `source` to `replyCount`. 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`. ```bash theme={null} 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 ``` ```json theme={null} { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "toolType": "reply_extractor", "status": "running" } ``` Store the creation response as a local handoff before polling: ```json theme={null} { "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`. ```bash theme={null} 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 `cursor` until `hasMore` is `false`. ## Step 4: Export CSV, JSON, or XLSX File exports do not charge credits after job creation. 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. ```bash theme={null} 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 ``` ```bash theme={null} 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 ``` ```bash theme={null} 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. ```bash theme={null} 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. ```bash theme={null} 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. ```bash theme={null} 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. ```javascript theme={null} 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 extraction jobs for saved files, credit estimates, or fixed `resultsLimit` caps. ## Choose the right reply output These frequently asked questions explain how to export replies. Preserve the original tweet, author, cursor, and engagement context. ### How do replies differ from quote tweets? Replies stay within conversation threads beneath the original tweet. Quote tweets create separate posts with added commentary. Store the original tweet ID with every reply row. Use the [Quote Tweets API](/api-reference/x/tweet-quotes) for quote tweets. Never merge both result sets without a field that identifies their relationship type. ### Why can public replies be missing? X can rank replies instead of showing them chronologically. It can also place probable spam behind an extra control. Protected, deleted, and hidden replies have separate visibility rules. Review the [official X reply guidance](https://help.x.com/en/using-x/mentions-and-replies). For API exports, follow every cursor and store each confirmed page. Treat a `424 replies_incomplete` response as evidence against complete coverage. ### How should a support team moderate reply rows? Keep reply text, author ID, username, creation time, and engagement counts. Route uncertain rows to a human review queue. Add sentiment or spam labels with your own classifier. Xquik does not return a sentiment verdict. CSV keeps spreadsheet review user friendly. JSON preserves nested media and author fields. ### When should I use the live replies API? Use the Twitter reply API when an application needs the latest cursor page. It returns reply tweets, authors, engagement counts, media, and the next cursor. Continue until `has_next_page` is `false`. New replies can arrive during pagination. Store each reply tweet ID and remove duplicate IDs downstream. ### When should I run a saved reply extraction? Choose `reply_extractor` for stored files and reviewable jobs. Estimate the parent tweet first. Then create one capped or uncapped extraction. Poll the returned job ID until completion. File conversion adds no credit charge. ### Which file format should I choose? Choose CSV for CRM imports and spreadsheet filters. Choose XLSX for analyst handoffs that require workbook tools. Choose JSON for applications that preserve nested reply fields. Convert JSON to JSON Lines when queues or warehouses need one reply per record. ### How do I resume an interrupted reply export? Persist the target tweet ID, extraction ID, current cursor, next cursor, and page index. Resume a saved job through `GET /extractions/{id}`. Resume live pagination with the last confirmed `next_cursor`. Never advance the checkpoint before storing its reply rows. ### How should I validate exported replies? Each reply contributes to the conversation under the parent tweet. Check that every row has a reply tweet ID and parent tweet ID. Preserve author IDs separately from usernames because usernames can change. Keep tweet creation times in UTC. Compare the returned rows with the estimate only for planning. The parent `replyCount` can change and does not guarantee the final export size. ## Cost and failure handling Estimate is free. A `reply_extractor` job costs 1 credit per result. Direct `GET /x/tweets/{id}/replies` calls cost 1 credit per returned tweet. Available credits can reduce the returned page size. File exports do not charge credits after job creation. Respect the read rate limit on live Twitter API pages. Use the parent reply count only for estimates. Treat returned reply rows as the source of truth. Handle common errors before retrying: Status `400`. Error `invalid_tweet_id`. Use a numeric tweet ID. Status `401`. Error `unauthenticated`. Send a valid `x-api-key`. Status `402`. Errors `no_subscription`, `subscription_inactive`, `no_credits`, or `insufficient_credits`. Subscribe, add credits, or lower `resultsLimit`. Status `429`. Error `rate_limit_exceeded`. Wait for `retryAfter` or the `Retry-After` header. Status `424` or `502`. Error `x_api_unavailable`. Retry with exponential backoff. ## Handoff checklist Export `format=csv` to `xquik-replies.csv` or `format=xlsx` to `xquik-replies.xlsx`. Export `format=json` to `xquik-replies.json`, convert it to `xquik-replies.jsonl`, or paginate `GET /extractions/{id}`. Set `resultsLimit` on create calls when you need a smaller run. Create an account or keyword monitor with `tweet.reply` events. ## Related reply APIs * [Plan saved tweet and reply exports](/guides/extraction-workflow) * [Create a reply extraction job](/api-reference/extractions/create) * [Fetch live tweet reply pages](/api-reference/x/tweet-replies) # Twitter Advanced Search API: CSV Export via REST Source: https://docs.xquik.com/guides/tweet-scraper-csv-export Install the Python SDK with pip install x_twitter_scraper. Follow this step by step flow for specific accounts. Use from:username for one Twitter account.
For the complete documentation index, see llms.txt.
* Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [X Article](/api-reference/x/get-article) * 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) * Profiles: [User tweets](/api-reference/x/user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Download media](/api-reference/x/download-media)
For hashtags search, pass the exact hashtag token. Respect API rate limits and X's Terms of Service. Store Twitter profiles separately from tweet rows. ## Export Twitter Data Define the export as tweet rows, author rows, or both. Keep each stable Tweet ID, text, author ID, creation time, and permalink. Add replies, reposts, likes, quotes, views, bookmarks, and media when returned. Store profile names, usernames, bios, verification, and audience counts in separate author columns. Use tweet\_search\_extractor for a durable job and downloadable files. Choose CSV, JSON, or XLSX. Save query, operators, language, date window, and result cap. Add extraction ID, completion status, and export time. Count unique Tweet IDs after download. Label limited windows and capped jobs clearly. Never call a bounded search export a complete account archive. Use CSV with Google Sheets. Use XLSX for analyst review. Use JSON for queues, warehouses, or application ingestion. ### How Do I Scrape Tweets Without Getting Blocked? Use the documented Twitter scraper API instead of browser evasion. Respect each rate limit and wait for retryAfter after a 429 response. Retry temporary 424 or 502 failures with exponential backoff. Keep the same query, filters, and cursor during retries. Changing them can skip or duplicate tweets. Set resultsLimit before large extraction jobs. This caps the requested tweets and the estimated credit cost. Store nextCursor only after saving every returned tweet. Resume from that checkpoint after a process restart. Never guess or modify an opaque cursor. Keep concurrent requests within the documented limits. Add jitter to delayed retries so several workers do not restart together. Stop retrying permanent API key, validation, or credit errors. Record the request ID and error body for diagnosis. This approach keeps exports moving without browser evasion, rotating identities, or unsupported anti-bot tactics. Keep the API key secret, and validate author IDs after every resumed page. Validate media URLs too. ### Twitter Scraper API Use the live Twitter search route for immediate cursor pages. Use tweet\_search\_extractor for estimates, durable job IDs, and status polling. It also provides saved rows and CSV, JSON, or XLSX files. Use a monitor when a keyword or account match must create a stored event. Every path should preserve Tweet IDs, author IDs, text, and timestamps. Add engagement counts, media, query filters, and page state. Keep the API key in a secret manager. Respect rate limits and documented error recovery. A Twitter scraper API removes browser maintenance. It cannot make unclear queries complete. It cannot turn a result cap into an archive. ### Scrape Tweets Python Use the [Python SDK](/sdks/python) or call REST with requests. Load the API key from a secret store. Send the keyword, hashtag, author, and language. Add the required date range, media, and engagement filters. Save returned tweets before advancing the opaque cursor. Normalize Tweet ID, text, author ID, username, and creation time. Add permalink, replies, reposts, likes, quotes, and media URLs. Deduplicate by Tweet ID. For saved jobs, estimate first. Then create, poll, and export the completed job. Retry 429, 424, or 502 only as documented. Never advance date checkpoints before the output becomes durable. ### Automate Tweet Export Store one explicit query and time window for each automated run. Estimate the requested result count. Create one extraction after the estimate passes the credit budget. Save its extraction ID before polling. Export only after the job reports completed. Write CSV, JSON, or XLSX to a dated, durable path. Record the query, filters, window, result cap, row count, extraction ID, status, and checksum. Count unique Tweet IDs before loading the destination. Advance the next time window only after the file upload succeeds. This prevents a failed handoff from creating an unseen gap. Validate author IDs, replies, reposts, and media before publishing the export. ### How Do I Build an Automated Twitter Data Pipeline With an API? Translate “data pipeline” into named records and checkpoints. Capture tweets, authors, replies, engagement counts, media URLs, and the exact search filters. Use stable Tweet IDs and author IDs as keys. Keep usernames and profile names as changeable attributes. The pipeline should estimate, create, poll, export, validate, and load. Save the extraction ID immediately. Retry status reads without creating another job. Verify terminal status, row count, and unique Tweet IDs. Check first and last timestamps plus required fields. Load files without creating duplicate Tweet IDs. Record the last durable date window or cursor. Send failed jobs to review with their documented error, request ID, and unchanged input. ### How to Schedule Recurring Tweet Exports Using a REST API Run one fixed interval through cron, a queue worker, or an orchestrator. Use separate UTC windows for routine exports. Add a small overlap only when the destination removes duplicate Tweet IDs. Save every window before starting its request. Call the estimate route and enforce approved result limits. Enforce credit limits too. Then create one extraction. Poll its ID to a terminal state. Download the selected format and validate unique Tweet IDs, timestamps, authors, and row counts. Upload the file, record its checksum, then advance the schedule checkpoint. Never advance after a timeout, failed extraction, invalid file, or incomplete destination upload. Check replies, reposts, and media columns before delivery. ### Twitter Data Pipeline Python Keep orchestration, API access, tweet row shaping, and destination writes in separate Python functions. One function builds the query and date window. One function estimates and creates the extraction. Another polls the stored ID. A final function validates and writes tweet rows. Use typed models for Tweet ID, author ID, text, and creation time. Include engagement counts, media URLs, query, and extraction ID. Persist status before every retry. Honor retryAfter for rate limits. Use exponential backoff for documented temporary failures. Upsert by Tweet ID in the destination. Commit the next window only after the file or transaction succeeds. This structure makes reruns safe and testable. ### Tweet Scraping Workflow Start with the search intent. Define keywords, hashtags, authors, language, dates, media type, and minimum engagement. Choose live search for immediate cursor pages. Choose an extraction job for estimates, saved results, and file exports. Choose a monitor for continuous account or keyword events. Store the request, API path, result cap, cursor or extraction ID, and execution time. Normalize Tweet IDs, author IDs, text, timestamps, engagement, media, and permalinks. Save each page before advancing its cursor. Remove duplicate Tweet IDs. Validate the first and last timestamps. Mark capped, interrupted, or credit-bounded results as partial. Route only validated rows downstream. ## Build a Scheduled Tweet Export Pipeline A recurring tweet scraping workflow needs an explicit search window. Store the keyword, author, language, start time, and end time for every run. Reuse those values for retries. Schedule one run through cron, Prefect, or another job runner. Call POST /extractions/estimate before creating the extraction. Stop when the estimate exceeds the approved result count or credit budget. Create one extraction for the approved window. Store its job ID immediately. Poll `GET /extractions/{id}` until the job completes or fails. Do not create a replacement job while the first job still runs. Export the completed rows once. Write the CSV, JSON, or XLSX file to a dated path. Record its extraction ID, search query, filters, row count, and format. Advance the schedule checkpoint only after the file becomes durable. A failed upload must not move the next start time. This rule prevents missing tweets between recurring runs. Python pipelines can use the Python SDK for direct pages. They can also call the REST job endpoints. Keep orchestration separate from tweet row shaping. This makes retries predictable across Python, CLI, and no-code runners. ## Validate Tweet Export Completeness Save the complete request before evaluating search results. Include the query, operators, language, date window, result limit, and export format. Two files with different filters should never share one comparison label. Count unique Tweet IDs after every export. Duplicate IDs may appear when date windows overlap or a retry restarts from an earlier cursor. Keep one canonical row for each Tweet ID. Preserve the newest complete author and engagement fields. Check the first and last tweet creation times. Compare them with the requested window. A bounded Twitter search can return fewer tweets than its result limit. This does not prove the job failed. Record the extraction status beside each file. A completed job supports an export. A failed job needs its returned error and a new decision. Never label partial search results as a complete archive. Review a small row sample before sending files downstream. Confirm tweet text, author username, author user ID, creation time, and permalink. Check likes, reposts, replies, quotes, views, and bookmarks when the response includes them. Validate attached media separately. Store media type and URL with the owning Tweet ID. Do not infer a missing image or video from tweet text. Keep user profiles separate from tweet identity. A username or display name can change. The author user ID remains the stable join key for later profile reads. Compare the exported row count with the stored job response. Investigate a difference before loading a warehouse. Common causes include duplicate Tweet IDs, rejected rows, file parsing errors, or an interrupted download. Use monitors when the workflow needs real-time events. A scheduled Twitter API export creates time-bounded snapshots. It should not claim instant coverage between completed runs. ## When to use this workflow Use this workflow for repeatable keyword, hashtag, account, or campaign exports. Choose it when teams need saved tweet rows, author profiles, engagement counts, media URLs, and an auditable file. Prefer direct tweet search for small live pages. Prefer monitors for continuous account or keyword alerts. ## Choose the right path Use extraction jobs for repeatable exports and audit trails. Use the direct API for low-latency pages, small app handoffs, or exact lookup from one stored Tweet ID or X status URL. Keep API keys outside code and logs. Store search results with tweet fields and user profiles. Respect the read rate limit before requesting another page. Import CSV exports into Google Sheets for shared analyst review. ## End-to-end export handoff Store one checkpoint that carries the search request through estimate, job creation, JSON pagination, and file export: Tweet search exports include base user fields, tweet fields, engagement counts, and metadata when available. ## Step 1: Estimate tweets and credits Call POST /extractions/estimate before scraping tweets. tweet\_search\_extractor requires searchQuery. Add resultsLimit when you want a sample or a hard cost cap. The estimate returns allowed, estimatedResults, creditsRequired, creditsAvailable, and source. For tweet search scraping, resultsLimit supplies source when you set a cap. Without a cap, source returns unknown. ## Filter fields to operators tweet\_search\_extractor merges structured fields into searchQuery before the job runs. Use fields when UI or SDK code owns filters; use advancedQuery when you already have a trusted X search operator string. Set exactPhrase to quote the value. Set excludeWords to turn comma-separated words into -word filters. The API appends advancedQuery to the final query. For direct GET /x/tweets/search, put the same operators in q. A plain Tweet ID or X status URL in q is a direct lookup, not a saved extractor job. Create the job with the same toolType, searchQuery, filters, and optional resultsLimit. Store the creation response as a local handoff before polling: Poll by tweet\_search\_extraction\_id; keep search\_query, filters, 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. Use the paginated response when your app wants JSON rows instead of a file download. ## Step 4: Export CSV, JSON, or XLSX Exports are free after the extraction job exists. Use CSV for spreadsheets, JSON for app ingestion, and XLSX for analyst handoff. CSV, JSON, and XLSX exports support up to 100,000 rows. ## Step 5: Hand off rows For API handoff, call `GET /extractions/{id}` with limit up to 1000. Pass `nextCursor` as `cursor` until `hasMore` is false. Store `job`, `results`, `hasMore`, and `nextCursor`. Normalize each tweet row before sending it downstream: For direct API handoff, store tweets\[].id, tweets\[].text, and tweets\[].createdAt. Also store tweets\[].author.id, tweets\[].author.username, has\_next\_page, and next\_cursor. For JSON Lines, write one normalized tweet object per line to xquik-tweet-search.jsonl. ## Direct tweet search API Use GET /x/tweets/search when you need a paginated API response instead of a stored extraction job. The direct search API returns tweets, has\_next\_page, and next\_cursor. Leave limit unset for a simple cursor-driven page loop, and pass next\_cursor back as cursor. Set limit when you want Xquik to collect up to that count in one bounded request. If has\_next\_page is true, continue with the same query, filters, queryType, and limit. Set cursor to next\_cursor. Cost is 1 credit per tweet returned. For account date windows, sinceTime and untilTime append since: and until: operators to the search query. A request such as 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 time-ordered backfills. Add a keyword, hashtag, or other operator when you need normal search ranking. When a bounded request sets limit, q=from:username without sinceTime or untilTime selects a user timeline pull. It returns a single page with has\_next\_page: false. Add another search term or use fromUser with a keyword when you need search pagination for that user. ## Failure handling Treat every failure as a stopped checkpoint, not a reason to skip tweets. Keep the exact query, filters, date window, cursor, extraction ID, and request ID. Never create a replacement extraction while the original job status remains unknown. Fix a 400 response before retrying. Check searchQuery, operators, dates, resultsLimit, and export format. Do not resend an unchanged invalid request. Replace an expired or invalid API key after a 401 response. Add credits after a 402 response, then continue from the saved checkpoint. Retry a documented 424 or 502 failure with exponential backoff and jitter. Keep the same extraction ID during status polling. For direct tweet search, keep the same query, filters, queryType, limit, and cursor. Changing those values can skip or duplicate tweets. Honor retryAfter after a 429 response. Pause only the affected worker. Do not start parallel requests for the same cursor page. Cap retries and send exhausted requests to review with their response status, request ID, and unchanged input. Treat a failed extraction status as terminal for that job. Save its returned error before deciding whether to create another extraction. Mark every interrupted export as partial. Preserve completed pages, unique Tweet IDs, and the last durable cursor. Validate the downloaded file before advancing any schedule checkpoint. Reject empty files when the completed job reported rows. Reject malformed CSV, JSON, or XLSX output. Keep the previous window open until the verified file reaches its destination. ## Handoff checklist Hand off the exact searchQuery, structured filters, advancedQuery, language, sinceTime, untilTime, queryType, and resultsLimit. Include the extraction ID for saved jobs. Include the final cursor for direct tweet search. Record the terminal status, export format, file name, checksum, byte size, row count, and unique Tweet count. Add the earliest and latest tweet creation times. Mark capped, credit-bounded, interrupted, and partial exports explicitly. Preserve Tweet ID, text, author ID, username, display name, and creation time. Keep replies, reposts, likes, quotes, views, bookmarks, permalinks, and media URLs when returned. Join refreshed profiles through stable author ID, not username. Name the downstream owner and destination. State whether the receiver expects CSV, JSON, XLSX, or JSON Lines. Document its duplicate key, required columns, timezone, and retry behavior. Confirm the destination accepted the file or rows before closing the window. Store the destination record count and load time. Investigate count differences before the next scheduled run. Advance the checkpoint only after this final confirmation. # TweetClaw OpenClaw Twitter Plugin for X Actions Source: https://docs.xquik.com/guides/tweetclaw Install the TweetClaw OpenClaw Twitter plugin. Search tweets, inspect profiles, export followers, monitor accounts, and approve posts, replies, DMs, and media.
For the complete documentation index, see llms.txt.
TweetClaw is Xquik's official OpenClaw Twitter plugin. It connects an OpenClaw agent to documented Xquik API endpoints. The plugin searches tweets, profiles, replies, followers, timelines, lists, communities, and trends. This OpenClaw Twitter skill supports posts, replies, likes, reposts, follows, and DMs. It also supports monitors, webhooks, follower exports, and media actions. It keeps API keys outside prompts and model-visible tool arguments. TweetClaw is an OpenClaw plugin, not an MCP server. Choose Xquik MCP for remote MCP clients. Choose an Xquik SDK for application code outside an agent runtime. ## Prerequisites * OpenClaw `2026.7.1` or newer * Node.js `22` or newer, within the installed OpenClaw release's supported range * [Xquik API key](/x-api-quickstart) for account-backed automation * Optional [MPP setup](/mpp/quickstart) for anonymous read-only pay-per-use calls Install agent plugins only from trusted sources. OpenClaw plugin installs run code. Official CLI docs recommend pinned production versions. ## Install the OpenClaw Twitter Plugin Install from Xquik's verified ClawHub publisher scope: ```bash theme={null} openclaw plugins install clawhub:@xquik/tweetclaw ``` OpenClaw records ClawHub as the tracked update source. Current bare package names use npm during the launch cutover. Use the explicit npm fallback for repeatable shared environments: ```bash theme={null} openclaw plugins install npm:@xquik/tweetclaw@1.6.41 --pin ``` `@xquik/tweetclaw` is the official package. The plugin id is `tweetclaw`. The published npm and source-truth versions are both `1.6.41`. Run `openclaw plugins update tweetclaw` for normal tracked updates. Exact npm pins stay fixed until you select another release. ## Configure API Key Auth Create an API key from the Xquik dashboard, then configure TweetClaw: ```bash theme={null} export XQUIK_API_KEY="xq_..." openclaw config set plugins.entries.tweetclaw.config.apiKey "$XQUIK_API_KEY" ``` API key auth unlocks account status, reads, monitors, webhooks, extractions, draws, and media. It also unlocks approved actions for connected X accounts. ## Configure MPP Pay-Per-Use MPP lets TweetClaw call 7 fixed-price, read-only X API endpoints. These routes need no Xquik account or API key. Use a guest `paid_reads` key for the broader 33-route prepaid catalog. ```bash theme={null} npm i mppx@0.8.12 viem@2.55.4 export MPP_SIGNING_KEY="0x..." openclaw config set plugins.entries.tweetclaw.config.tempoSigningKey "$MPP_SIGNING_KEY" ``` MPP mode is read-only. Use API key auth for private reads and account state. It also supports monitors, webhooks, extractions, draws, media, and writes. ## Optional Settings TweetClaw can poll Xquik events and surface monitor notifications in chat. ```bash theme={null} openclaw config set plugins.entries.tweetclaw.config.pollingEnabled true openclaw config set plugins.entries.tweetclaw.config.pollingInterval 60 ``` Use the default base URL unless you operate a private Xquik deployment: ```bash theme={null} openclaw config set plugins.entries.tweetclaw.config.baseUrl "https://xquik.com" ``` ## Tools Search the bundled Xquik endpoint catalog and inspect parameters. This tool does not call the network. Call catalog-listed Xquik endpoints with structured method, path, query, and body input. This tool can make network requests. The `explore` tool accepts natural language catalog queries. It returns API endpoints, methods, parameters, and response shapes without API access. The `tweetclaw` tool sends structured API calls after authentication. Use `explore` before every live call. Confirm the route, target, limit, and account. Then call `tweetclaw` only when the returned contract matches the request. This split keeps endpoint discovery free and read-only. The `explore` tool is the safe first step. Inspect the catalog before calling an endpoint: ```text theme={null} Find the endpoint for searching tweets about AI agents, then show the required parameters. ``` The `tweetclaw` tool can spend credits or read private account details. It can also perform write-like actions. Allow this optional tool explicitly: ```bash theme={null} openclaw config set tools.alsoAllow '["explore", "tweetclaw"]' ``` OpenClaw plugins may stay hidden under restrictive tool profiles. The `tools.alsoAllow` config keeps existing coding tools and adds TweetClaw. ## Run OpenClaw Twitter Workflows ### Search and Read Tweets An OpenClaw Twitter search starts with a bounded query. Ask `explore` for the tweet search route. Then pass the exact query and result limit to `tweetclaw`. Preserve every tweet ID, author username, timestamp, metric, and cursor. Pass `next_cursor` back unchanged. Stop when `has_next_page` becomes false. Use timelines for one account's recent tweets. Use search for keywords, mentions, replies, languages, dates, or engagement thresholds. Private reads require an authorized X account. ### Export Followers and Replies Use extraction jobs for large follower, following, or reply exports. Estimate the job first. Confirm the target username, tweet ID, and maximum result count. Create the extraction only after approval. Poll its status endpoint until the job finishes. Store the extraction ID and every export URL. CSV supports spreadsheets, JSON preserves nested fields, and XLSX supports business handoffs. ### Monitor Accounts and Keywords Account monitors capture new tweets from selected profiles. Keyword monitors capture matching tweets for a stored query. Both create recurring usage. Approve the monitor definition before creation. Store its monitor ID. Replay events with the returned cursor. Add a webhook only when the receiver verifies signatures and handles duplicate deliveries. Treat an API rate limit separately from dependency failures. Preserve completed tweet IDs and the current cursor. Resume only after the documented reset. ### Post Tweets and Replies To make OpenClaw post to Twitter, connect an X account first. Ask the agent to draft the exact text. Review the selected account, reply target, and media. Approve the structured call once. Send a unique `Idempotency-Key`. Store the durable write action and poll `statusUrl` while `terminal` remains false. An OpenClaw Twitter bot must not publish unattended posts. Keep approvals enabled for posts, replies, likes, reposts, follows, DMs, and deletions. ### Upload Media and Send DMs Tweet and reply actions accept public HTTPS image or MP4 URLs in `media`. Direct messages use an uploaded `mediaId` inside `media_ids`. Keep DM bodies outside shared logs and handoffs. Store only required resource IDs, action status, and retry guidance. Never resend a pending write. ## Workflow Handoffs Use `explore` first. Then call `tweetclaw` for one intended endpoint, target, and limit. Estimate `reply_extractor` with `targetTweetId`. Create the extraction and poll `/api/v1/extractions/{id}`. Return CSV, JSON, and XLSX export URLs. Estimate `follower_explorer` with `targetUsername`. Create the extraction and poll until completion. Export the job for CRM or warehouse import. Use `explore` to find monitor and webhook endpoints. Call `tweetclaw` for `POST /api/v1/monitors` or `POST /api/v1/monitors/keywords`. Call `POST /api/v1/webhooks` only after approval. Store its `secret` in a secret manager. Receivers must verify `X-Xquik-Signature`. Store `deliveryId` and `streamEventId`. Return `2xx` for accepted duplicates. Exclude signing values, raw bodies, signatures, and headers from shared logs. For tweets or replies, call `POST /api/v1/x/tweets` with public media URLs in `media`. Store the durable action `id`, `status`, `billing`, `result`, and `statusUrl`. Poll while `terminal` is false. Upload DM media first. Pass the returned `mediaId` as the one-item `media_ids` value. Store the DM action. Exclude full DM bodies. Leave `reply_to_message_id` unset. ```text theme={null} Use explore to find reply_extractor extraction endpoints. Estimate replies for tweet 1893704267862470862. Create the job with targetTweetId and resultsLimit 500 only if allowed. Return extraction id, status, poll URL, and CSV, JSON, and XLSX export URLs. ``` ```text theme={null} Use explore to find follower_explorer extraction endpoints. Estimate followers for @username with resultsLimit 10000. Create the job only if allowed. Return extraction id, status, poll URL, and CSV, JSON, and XLSX export URLs. ``` ```text theme={null} Use explore to find monitor and webhook endpoints. Create an account monitor or keyword monitor only after approval. Register the receiver URL with POST /api/v1/webhooks. Store the webhook secret in a secret manager. Verify X-Xquik-Signature, store deliveryId and streamEventId, and return 2xx for accepted duplicates. Keep endpoint signing values, raw request body, raw signature, and full headers out of chat logs and shared workflow outputs. ``` ```text theme={null} Use explore to find media write endpoints. For a tweet or reply, call POST /api/v1/x/tweets with media set to public HTTPS image or MP4 URLs. Do not send media_ids. Send a unique Idempotency-Key. Store id, status, billing, result, and statusUrl. Poll while terminal is false. Retry only when safeToRetry is true, using a new key. For a DM attachment, call POST /api/v1/x/media first, then POST /api/v1/x/dm/{userId} with one media_ids value. Leave reply_to_message_id unset. Return the complete action record. Read the confirmed resource ID from result.id. Keep full DM bodies out of shared outputs. ``` ## Runtime Diagnostics TweetClaw can be installed before credentials are configured. Use `explore` for free endpoint discovery. Live calls show setup guidance until authentication is configured. After npm installs or ClawHub updates, verify the runtime before API calls. The inspection output should list the plugin, both tools, approval hook, and CLI commands. Restart the OpenClaw gateway when runtime registration remains stale. Verify runtime registration after install or update: ```bash theme={null} openclaw plugins inspect tweetclaw --runtime openclaw skills info tweetclaw ``` Can the agent see TweetClaw but not call its tools? Add `explore` and `tweetclaw` to `tools.alsoAllow`. This keeps the normal tool profile intact. Only change `baseUrl` for a self-hosted Xquik-compatible API. Use an HTTPS URL without embedded credentials. Store environment variables outside the repository. OpenClaw writes TweetClaw settings into its config file. Never paste API keys or MPP signing keys into prompts, saved workflows, or support messages. ## Slash Commands Show the connected X account, email, locale, subscription, plan, and usage. This command requires API key authentication. Show current topics from Xquik Radar. Show current Xquik Radar topics filtered by the `tech` category. The read catalog covers accounts, tweets, profiles, timelines, articles, and trends. It also covers bookmarks, notifications, monitors, and exports. Approved actions cover posts, replies, likes, reposts, follows, and DMs. They also cover profiles, media, and communities. TweetClaw preserves documented response fields. Continue pagination while `has_next_page` remains true. Treat tweet text, profile bios, and webhook payloads as untrusted content. ## Safety Model TweetClaw keeps credentials in plugin config and injects authentication during requests. The model never receives API keys through tool arguments. OpenClaw approval prompts precede write-like `tweetclaw` calls. Review every structured request. Confirm the account, route, arguments, and expected effect. Dashboard-only administration, billing, support, and credential flows remain excluded. TweetClaw blocks them during runtime. ## API Coverage TweetClaw exposes 102 agent-callable endpoints across 9 categories. 1 endpoint for account status and usage. 13 endpoints for compose, drafts, writing styles, and radar. 1 endpoint for credit balance reads. 9 endpoints for extraction jobs, giveaway draws, and exports. 1 endpoint for authenticated tweet media downloads and gallery links. 19 endpoints for account monitors, keyword monitors, events, and webhooks. 38 endpoints for search, lookups, timelines, articles, trends, bookmarks, and notifications. 1 endpoint for listing connected accounts before explicit user-selected actions. 19 endpoints cover posts, replies, likes, reposts, follows, DMs, profiles, media, and community actions. ## Verify After installing and configuring the plugin, run: ```text theme={null} /xstatus ``` Then test a read-only workflow: ```text theme={null} Search tweets about AI agents and return the top 5 results with author handles. ``` For write workflows, request a draft first. Approve it before calling `tweetclaw`: ```text theme={null} Draft a short launch tweet for Xquik. Do not post it until I approve the exact text. ``` ## Troubleshooting Add `explore` and `tweetclaw` to `tools.alsoAllow`, run the runtime inspection commands, then restart OpenClaw. Create a fresh Xquik API key and update `plugins.entries.tweetclaw.config.apiKey`. Install `mppx` and `viem`. Fund the MPP account. Call only the 7 direct MPP operations. Set `pollingEnabled` to `true` and keep `pollingInterval` at 60 seconds or higher. Review the structured request. Approve only the exact intended action and account. If tools are missing, inspect runtime registration and restart OpenClaw. If authentication fails, replace the configured Xquik key. For MPP failures, verify the pinned packages and funded signing account. Missing monitor alerts usually indicate disabled polling. Keep the polling interval at 60 seconds or higher. Write approval prompts are expected safety gates, not runtime errors. ## OpenClaw Twitter Questions ### Can OpenClaw Use Twitter? Yes. TweetClaw provides catalog-backed Twitter operations through Xquik. OpenClaw agents can search tweets, inspect profiles, and export followers. They can also monitor accounts and request approved actions. ### Can OpenClaw Read Twitter? Yes. Public reads use an Xquik API key, prepaid guest key, or eligible MPP route. Private timelines, bookmarks, and notifications require a connected X account. ### How Do I Connect OpenClaw to Twitter? Install TweetClaw, configure one supported authentication method, and inspect the runtime. Start with `explore`. Allow `tweetclaw` only when live API access is required. ### Is TweetClaw an OpenClaw Twitter Skill? Yes. The package includes an OpenClaw Twitter skill and plugin runtime. The skill explains usage. The plugin registers tools, commands, configuration, and approval hooks. The package also serves as an OpenClaw X Twitter skill for catalog-guided tasks. ### Is TweetClaw an OpenClaw Twitter Plugin or MCP Server? TweetClaw is an OpenClaw Twitter plugin. TweetClaw does not implement MCP. Remote MCP clients should connect directly to `https://xquik.com/mcp`. ### Can OpenClaw Post to Twitter? Yes, after connecting an X account. Keep per-call approval enabled. Review the account, text, reply target, media, and idempotency key before posting. ### Can TweetClaw Run Read-Only? Yes. Use `explore` for offline catalog search. Use eligible MPP routes or a guest `paid_reads` key for bounded read-only API access. ### How Does TweetClaw Handle API Rate Limits? Keep the completed tweet IDs and current cursor. Wait for reset guidance. Resume from the saved cursor without restarting the workflow. ### How Do I Update the OpenClaw Twitter Integration? Run `openclaw plugins update tweetclaw`. OpenClaw reuses the tracked ClawHub or npm source. Exact npm pins remain fixed until you choose another version. ## References * [OpenClaw plugin CLI](https://docs.openclaw.ai/cli/plugins) * [Hermes Tweet for Hermes Agent](/guides/hermes-tweet) * [Xquik Billing](/guides/billing) * [MPP Quickstart](/mpp/quickstart) * [API Reference](/api-reference/overview) # Twitter Comment Picker & Retweet Giveaway API Source: https://docs.xquik.com/guides/twitter-comment-retweet-picker Build Twitter comment, retweet, and hashtag picker entry lists from reply authors and retweeters. Preserve cursors, user IDs, filters, and selection proof.
For the complete documentation index, see llms.txt.
Build reviewable giveaway entry lists from replies and retweets. Keep stable user IDs, Tweet IDs, cursors, filters, and checked timestamps. Then create one draw from the source Tweet URL and published filters. ## How Does a Twitter Comment Picker Work? A Twitter comment picker treats direct reply authors as potential entries. Use [Get Tweet Replies](/api-reference/x/tweet-replies) for reply text and authors. Page until `has_next_page` is `false`. Pass `next_cursor` back as `cursor`. Store every reply ID, author ID, text, timestamp, and source Tweet ID. Remove duplicate authors before selection when campaign rules require uniqueness. Apply only published keyword, hashtag, mention, language, and account filters. A random comment picker selects winners from the remaining stable user IDs. It cannot prove follows or retweets without separate API checks. ## How Do I Pick a Winner From Twitter Comments? A random Twitter comment picker retrieves every direct reply page. To pick a winner from Twitter comments, store each reply ID and author ID. Reject replies posted after the published closing time. A Twitter reply picker should freeze eligible user IDs before selection. Set the number of winners and backup winners once. Create one draw to pick a random winner from filtered reply entries. Use the [Twitter giveaway picker](/guides/twitter-giveaway-picker) with the source Tweet URL. Send every published filter in the original create request. Store those giveaway rules beside the returned draw ID. `GET /draws/{id}` does not repeat the create-time filters. After completion, export `type=winners` for stored winners and backups. Read those stored records instead of running another random selection. Search draw history when the create response is uncertain. One stored draw prevents conflicting random winners. Export the giveaway winner and every backup position. Never pick multiple winners through separate speculative draws. This winner selection process stays tied to the original reply evidence. ## How Does a Twitter Retweet Picker Verify Entries? Use a Twitter retweet giveaway picker when published entry rules require reposts. A Twitter retweet picker reads stable user IDs from the retweeter list. Use [Get Retweeters](/api-reference/x/retweeters) to retrieve every available page. Store each retweeter ID, page cursor, source Tweet ID, and checked timestamp. Do not trust a displayed repost count as participant proof. Set `mustRetweet` only when the published rules require a repost. Join retweeter IDs with reply-author IDs when campaigns require both actions. Keep the join result beside both original entry sources. This record shows why each participant entered the winner pool. ## How Does a Twitter Hashtag Giveaway Picker Work? A Twitter hashtag giveaway picker checks required hashtags in reply text. Use a specific hashtag only when published campaign rules require it. Store the matched reply ID, author ID, hashtag, and checked timestamp. Keep unmatched rows when an operator needs rejection evidence. Normalize hashtag comparisons according to the published campaign rules. Record original reply text beside every match and rejection. Preserve letter case when the published rule requires exact matching. Hashtag text cannot prove follows or retweets. Run those checks separately. Apply all required checks before random selection begins. ## Can a Comment Picker Verify Likes? No. The current Draws contract does not accept likes as an eligibility rule. Do not claim that a comment picker verifies likes. Use only documented follow, retweet, keyword, hashtag, mention, and account rules. The contract also supports documented language and unique-author conditions. It supports account-age and follower-count thresholds when campaigns publish them. Do not launch unsupported selection when likes define campaign entry. Rewrite rules around supported signals before the campaign opens. Never substitute replies for likes after participants enter. ## Combine Comment and Retweet Checks 1. Store one source Tweet URL and numeric Tweet ID. 2. Retrieve every reply page and every retweeter page. 3. Normalize each participant to a stable X user ID. 4. Remove duplicates according to published uniqueness rules. 5. Apply required reply text and account filters. 6. Join required retweet and follow results by user ID. 7. Store accepted and rejected entries with reasons. 8. Create one draw with the source Tweet URL and published eligibility filters. Never change filters after the entry deadline. Never replace stable IDs with names. Repeat uncertain reads with the same Tweet ID, filter, and cursor. ## Store an Entry Audit Row Keep one row for each checked participant and entry source. ```json theme={null} { "tweet_id": "1893704267862470862", "x_user_id": "9876543210", "reply_id": "1893705000000000000", "retweet_verified": true, "required_hashtag_verified": true, "verification_state": "eligible", "checked_at": "2026-05-24T19:30:00.000Z" } ``` Store cursor checkpoints outside participant rows. One checkpoint can resume each page. Keep private notes and API keys outside any public result URL. ## Next Steps Page reply text and stable author IDs. Page stable user IDs for repost checks. Select winners and backup winners after verification. Combine every campaign proof source. # Twitter Giveaway Picker API for Verified Winners Source: https://docs.xquik.com/guides/twitter-giveaway-picker Select Twitter giveaway winners from checked replies, retweets, follows, keywords, hashtags, mentions, and account rules. Export entries and winner proof.
For the complete documentation index, see llms.txt.
Use Xquik as a Twitter giveaway picker with reviewable entry checks. One draw stores filters, winners, backup winners, timestamps, and exports. It can also publish a result URL for participant review. ## What Makes the Best Twitter Giveaway Picker? The best Twitter giveaway picker records every published rule before selection. Store the source Tweet ID, winner count, backup count, and unique-author rule. Keep each follow, retweet, keyword, hashtag, mention, language, and account filter. Preserve inspected entry IDs, selected winner IDs, status, and draw ID. Stable user IDs matter more than display names or handles. Avoid tools that return only winner names. Those names cannot prove eligibility. A useful giveaway tool exports inspected entries and selected winners separately. It also keeps a public result URL when campaigns require transparency. ## How Do I Automate a Twitter Giveaway With an API? Write every campaign rule before collecting entries. Identify one source tweet. Define `winnerCount`, optional `backupCount`, and `uniqueAuthorsOnly`. Add only the filters shown to participants. Call [Create Draw](/api-reference/draws/create) once. Save the returned draw ID. Poll that draw until it completes or fails. Then export its records. Use `type=entries` for inspected participants. Use `type=winners` for selections. Treat an unknown create response as an investigation. Search draw history first. Do not create another draw because a client lost its local response. Keep API keys and private notes outside any public result record. ## Programmatic Twitter Giveaway Draw Checklist 1. Store the source Tweet URL and numeric Tweet ID. 2. Record the entry closing time before collection starts. 3. Define the winner and backup counts. 4. Publish every required follow, retweet, keyword, hashtag, or mention. 5. Publish any language, account-age, or follower-count threshold. 6. Collect replies, retweeters, quotes, or followers. 7. Remove duplicate authors when rules require unique participants. 8. Apply every published rule before random selection. 9. Create one draw and save its ID immediately. 10. Export entries, winners, backup winners, filters, and timestamps. Never draw from an unchecked follower or reply snapshot. Exclude inactive or spam accounts only through published filters. More engagement never weakens the proof requirements. ## How Do I Pick a Winner From Replies and Retweets? A Twitter picker should build one eligible pool before it picks a winner. Start with one giveaway post and its numeric Tweet ID. Collect reply authors and retweeters through their separate paginated endpoints. Store each cursor beside the page it produced. Record the collection start and closing times. Reject late replies and retweets using the published closing boundary. Keep each rejected user ID with its failed check. Store no extra profile details unless a published filter needs them. When a Twitter contest requires both actions, join participants by stable user ID. Keep entries only when both source lists contain that ID. For a specific hashtag, check the original reply text. Store the matching reply ID and checked timestamp. Never infer hashtag compliance from aggregate engagement counts. A retweet picker alone can produce winners from retweets. It cannot confirm replies, follows, hashtags, or account thresholds. Run every published check before any participant is selected randomly. Then freeze the eligible pool and create one draw. This sequence lets teams run giveaways without changing rules after closing. If the create response becomes uncertain, search draw history. Reuse the stored draw ID when the request succeeded. Never pick a winner through a second speculative draw. That duplicate draw could return a conflicting winner. Export inspected entries first, then export final winners. Compare their counts with the stored request. ## How Does a Twitter Random Giveaway Picker Choose Winners? A Twitter random giveaway picker filters entries before random selection. Set the number of winners and backup count in one request. The draw randomly selects eligible stable user IDs after every check. Never randomly pick from an unchecked follower, reply, or retweeter list. A Twitter giveaway winner picker should preserve the starting entry count. Keep every selected user ID, position, backup flag, filter, and timestamp. Export the original request beside inspected entries and final winners. These records explain why each participant entered the eligible pool. ## How Do I Prove Giveaway Winners Were Eligible? Export `type=entries` and `type=winners` after the draw completes. Keep the draw ID, source Tweet ID, participant IDs, and applied filters. Record timestamps, verification states, winner positions, and backup flags. Share the public result URL when participants need reviewable proof. Reconcile exported user IDs with inspected entry counts before publishing. Keep rejection reasons for participants excluded by any published filter. Preserve export filenames and formats with the campaign record. Do not publish API keys, private notes, or unnecessary participant details. Keep those records within the campaign audit. ## What Should I Publish With the Winner? Publish the winner handle, giveaway tweet, and result URL when appropriate. Keep the verified user ID and draw ID in your private audit. Explain any published eligibility filter that affected selection. Never publish hidden rules because hidden rules should never exist. Publish the closing time, winner position, and backup status when required. Link results to the source tweet for participant context. Avoid publishing rejected entry details or complete participant exports. Keep those records within the private campaign audit. ## Next Steps Select winners with documented campaign rules. Download inspected entries or selected winners. Combine follows, retweets, replies, quotes, and audit rows. Verify reply and retweet entry sources. # Test Twitter Webhooks, Signatures & Retry Events Source: https://docs.xquik.com/guides/twitter-webhook-testing Test signed tweet, follower, profile, relationship, and keyword monitor webhook deliveries with a local server, ngrok, or webhook.site. Follow exact steps.
For the complete documentation index, see llms.txt.
Test your webhook integration before deploying to production. Use Xquik's signed test delivery, local HTTPS tunnels, payload inspectors, and delivery logs to verify handlers before real monitor events arrive. ## Test with Xquik first Use the [Test Webhook](/api-reference/webhooks/test) endpoint after you create a webhook. Xquik sends a real `webhook.test` delivery to the configured URL with the same `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` headers used for production monitor events. `webhook.test` payloads contain `eventType`, `data.message`, and `timestamp`; they omit `deliveryId` and `streamEventId`, so use them for reachability and signature checks, not production de-dupe. ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks/15/test \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` **Response when your endpoint accepts the delivery:** ```json theme={null} { "success": true, "statusCode": 200 } ``` **Response when your endpoint rejects the delivery:** ```json theme={null} { "success": false, "statusCode": 500, "error": "HTTP 500" } ``` Check your server logs for the `webhook.test` payload, verify the HMAC before processing it, and return a 2xx response only after your handler accepts the event. ## End-to-end handoff check Use a signed test delivery to prove the receiver can verify requests, then use production monitor deliveries to prove idempotency and storage behavior. ```json theme={null} { "workflow": "signed_webhook_test_handoff", "webhook": { "webhook_id": "15", "url": "https://example.com/xquik/webhook", "event_types": ["tweet.new", "tweet.reply"], "secret_storage": "store_once" }, "test_delivery": { "endpoint": "/api/v1/webhooks/15/test", "event_type": "webhook.test", "has_delivery_id": false, "has_stream_event_id": false, "signature_headers": [ "X-Xquik-Signature", "X-Xquik-Timestamp", "X-Xquik-Nonce" ], "receiver_status": 200, "success": true }, "production_delivery": { "event_type": "tweet.new", "delivery_id": "502", "stream_event_id": "9002", "dedupe_keys": ["deliveryId", "streamEventId"], "return_status": "2xx_before_slow_work" }, "delivery_triage": { "endpoint": "/api/v1/webhooks/15/deliveries", "status": "failed", "attempts": 3, "last_status_code": 500, "last_error": "Internal Server Error" }, "event_context": { "endpoint": "GET /api/v1/events/{id}", "id_source": "streamEventId", "store": ["monitorId", "monitorType", "type", "occurredAt", "data"] }, "handoff_state": "verified_signature_then_join_event_context" } ``` Store the webhook `secret` once. Verify the raw body with `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` before parsing or queueing the event. Treat `webhook.test` as a signed reachability check. It includes `eventType`, `data.message`, and `timestamp`, and omits `deliveryId` and `streamEventId`. Use `deliveryId` for receiver retry de-dupe. Use `streamEventId` when one monitor event should process once across endpoint changes. Query `GET /api/v1/webhooks/{id}/deliveries` for `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt` before paging or replaying work. Use delivery `streamEventId` as the `{id}` for [Get Event](/api-reference/events/get). Store the event `monitorId`, `monitorType`, `type`, `occurredAt`, and `data` with the receiver incident. ### Receiver replay row Store signed test deliveries separately from production delivery rows. A `webhook.test` row proves reachability and HMAC verification only. Production rows carry the IDs your receiver needs for retry de-dupe, event de-dupe, and event detail joins. ```json theme={null} { "record_type": "webhook_receiver_replay_check", "webhook_id": "15", "test_payload_has_ids": false, "production_delivery_id": "502", "production_stream_event_id": "9002", "signature_verified": true, "nonce_cache_key": "webhook:15:nonce_hash", "event_join": "GET /api/v1/events/9002", "delivery_log": "GET /api/v1/webhooks/15/deliveries", "shared_storage_excludes": [ "endpoint_signing_values", "raw_request_body", "raw_signature", "full_headers" ] } ``` Use this row after verification succeeds and before slow downstream work. Keep the raw body available for the HMAC check, then store only the de-dupe IDs, join route, verification state, and sanitized delivery context in shared systems. ## Local testing with ngrok [ngrok](https://ngrok.com) creates a public HTTPS tunnel to your local server, letting Xquik deliver webhooks to your development machine. ```bash theme={null} # macOS brew install ngrok # Linux curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok-v3-stable-linux-amd64.tgz \ | tar -xz -C /usr/local/bin # Authenticate (free account required) ngrok config add-authtoken YOUR_NGROK_TOKEN ``` Run your webhook handler on a local port (e.g. `:3000`): ```bash theme={null} node server.js # or: python app.py # or: go run main.go ``` ```bash theme={null} ngrok http 3000 ``` ngrok outputs a public HTTPS URL: ```text theme={null} Forwarding https://a1b2c3d4.ngrok-free.app → http://localhost:3000 ``` Copy the `https://` URL. ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://a1b2c3d4.ngrok-free.app/webhook", "eventTypes": ["tweet.new", "tweet.reply"] }' | jq ``` Save the `secret` from the response for signature verification. Trigger a signed `webhook.test` request through the tunnel before waiting for real tweet events: ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks/15/test \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Inspect your app logs and the ngrok web inspector at `http://localhost:4040`. When a monitored account posts a tweet, Xquik delivers the event through ngrok to your local server. Check your server logs and the ngrok web inspector at `http://localhost:4040`. ngrok URLs change every time you restart the tunnel (free plan). Update your webhook URL after each restart, or use a paid ngrok plan for stable subdomains. ## Testing with webhook.site Use [webhook.site](https://webhook.site) to inspect webhook payloads without running a local server. Visit [webhook.site](https://webhook.site). A unique HTTPS URL is generated automatically: ```text theme={null} https://webhook.site/a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://webhook.site/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "eventTypes": ["tweet.new"] }' | jq ``` When events arrive, they appear in the webhook.site dashboard in real time. Inspect headers (`X-Xquik-Signature`, `Content-Type`) and the JSON body to verify the payload format matches your expectations. webhook.site is useful for inspecting payload structure. For testing signature verification and handler logic, use [ngrok with a local server](#local-testing-with-ngrok) instead. ## Sending test payloads Simulate a webhook delivery to your local handler without calling Xquik. This is useful for unit tests or offline debugging. For end-to-end verification of the configured webhook URL, prefer `POST /webhooks/{id}/test`. ```bash theme={null} # Generate a test signature SECRET="your_webhook_secret_here" PAYLOAD=$(cat <<'JSON' {"eventType":"tweet.new","schemaVersion":1,"deliveryId":"502","streamEventId":"9002","occurredAt":"2026-02-24T14:22:00.000Z","username":"elonmusk","data":{"id":"1893456789012345678","text":"The future is now.","author":{"id":"44196397","userName":"elonmusk","name":"Elon Musk"},"isRetweet":false,"isReply":false,"isQuote":false,"createdAt":"2026-02-24T14:22:00.000Z"}} JSON ) TIMESTAMP=$(date +%s)000 NONCE=$(openssl rand -hex 16) SIGNATURE="sha256=$(printf '%s.%s.%s' "$TIMESTAMP" "$NONCE" "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)" # Send to your local server curl -X POST http://localhost:3000/webhook \ -H "Content-Type: application/json" \ -H "X-Xquik-Timestamp: $TIMESTAMP" \ -H "X-Xquik-Nonce: $NONCE" \ -H "X-Xquik-Signature: $SIGNATURE" \ -d "$PAYLOAD" ``` **Test payload structure:** ```json theme={null} { "eventType": "tweet.new", "schemaVersion": 1, "deliveryId": "502", "streamEventId": "9002", "occurredAt": "2026-02-24T14:22:00.000Z", "username": "elonmusk", "data": { "id": "1893456789012345678", "text": "The future is now.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": false, "isReply": false, "isQuote": false, "createdAt": "2026-02-24T14:22:00.000Z" } } ``` Include `deliveryId` and `streamEventId` in offline fixtures so receiver idempotency tests match production deliveries. Test all event types by changing the `eventType` field: `tweet.new`, `tweet.reply`, `tweet.quote`, `tweet.retweet`. ## Debugging delivery failures ### Check delivery status Query the [deliveries endpoint](/api-reference/webhooks/deliveries) to see delivery attempts and error details: ```bash theme={null} curl https://xquik.com/api/v1/webhooks/15/deliveries \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` **Response:** ```json theme={null} { "deliveries": [ { "id": "502", "streamEventId": "9002", "status": "failed", "attempts": 3, "lastStatusCode": 500, "lastError": "Internal Server Error", "createdAt": "2026-02-24T14:25:00.000Z" } ] } ``` Join `streamEventId` to [Get Event](/api-reference/events/get) when a receiver owner needs the original monitor event behind a failed or exhausted delivery. ```bash theme={null} curl https://xquik.com/api/v1/events/9002 \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` Store the event `monitorId`, `monitorType`, `type`, `occurredAt`, and `data` with the delivery `id`, `status`, `attempts`, `lastStatusCode`, and `lastError` before paging support, queue, or receiver owners. ### Reconcile missed deliveries Use delivery rows for receiver attempts and event pages for stored monitor history. The delivery `streamEventId` is the event `id`, so a failed receiver can rebuild its downstream queue from stored events after the handler is fixed. ```json theme={null} { "record_type": "webhook_delivery_reconciliation", "delivery_endpoint": "GET /api/v1/webhooks/15/deliveries", "event_backfill_endpoint": "GET /api/v1/events?limit=100&cursor={nextCursor}", "join_key": "delivery.streamEventId == event.id", "store": [ "deliveryId", "streamEventId", "status", "attempts", "eventId", "nextCursor" ], "next_action": "rebuild_receiver_queue_after_handler_fix" } ``` Store `nextCursor` after each event page. Continue with `GET /api/v1/events?limit=100&cursor={nextCursor}` until `hasMore` is `false`, then compare event IDs with delivery `streamEventId` values before replaying your own downstream work. ### Delivery statuses Queued for the next delivery attempt. Check recent deploys, tunnel uptime, and receiver availability before forcing a new test. Your endpoint returned `2xx`. Confirm your handler verified the signature and stored the event before it returned success. The latest attempt failed and is retrying with backoff. Inspect `lastStatusCode`, `lastError`, and your receiver logs. All retry attempts are used, or the receiver returned `410 Gone`. Fix the endpoint, then send a new signed test delivery. ### Common failure reasons We recommend responding within 10 seconds. If your handler is slow, return `200` immediately and process the event asynchronously using a background job queue. Any response outside the 200-299 range counts as a failure. Check your server logs for unhandled exceptions or validation errors in your handler. Common culprits: missing middleware (e.g. `express.raw()`), JSON parse errors, or database connection failures. The webhook URL hostname could not be resolved. Verify your domain DNS records are correct. If using ngrok, confirm the tunnel is still running. Webhook URLs must use HTTPS with a valid certificate. Self-signed certificates are rejected. Use a trusted CA (Let's Encrypt, Cloudflare) or ngrok for local development. The target server is not accepting connections. Verify your server is running and listening on the correct port. Check firewall rules if running on a cloud provider. Compute the HMAC over the **raw request body bytes**, not a re-serialized JSON object. Re-serialization can alter whitespace or key ordering. Use `express.raw()` in Node.js, `request.get_data()` in Flask, or `io.ReadAll(r.Body)` in Go. How webhooks work, delivery format, and retry policy. HMAC-SHA256 verification in Node.js, Python, and Go. Query delivery attempts and statuses. # X API Workflows for Tweets, Followers & Webhooks Source: https://docs.xquik.com/guides/workflows Choose workflows for tweet lookup, search, follower export, profile monitors, signed webhooks, MCP agents, file exports, or DMs. Includes exact API steps.
For the complete documentation index, see llms.txt.
Use these X API workflows for dashboards, webhooks, agents, exports, and content. Choose the handoff here, then open the focused workflow or API page for copy-ready examples. Run the [API checklist](/guides/x-api-integration-checklist) before launch. Batch processing, dashboards, and low-frequency checks. Interval-based, low setup effort. Alerts, queues, support triage, and warehouse sync. Medium setup effort. Tweet search, user lookups, follower checks, and research summaries. Low setup effort. Post tweets, post tweet replies, and hand off public media URLs. Low setup effort. Draft generation and reply monitoring. Low setup effort. ## Choose a Workflow Use monitor polling or webhooks. Returns: tweets, replies, quotes, and retweets. Use signed webhooks. They deliver event payloads to queues or support tools. Use the MCP path. Returns: tweet search results, user profiles, and monitor events. Use extraction workflows. Returns: CSV, JSON, XLSX, or paginated JSON. Use the follower export CRM workflow. Returns: CSV, XLSX, JSON, and a CRM field map. Use the create tweet workflow. Returns: published `tweetId`, `success`, `charged`, and `chargedCredits`. Use tweet composition. Returns: algorithm guidance, refined text, and score. Use draws. Returns: eligible participants, winners, and archived tweet metrics. ## Use-Case Endpoint Finder Start here when a user asks which Xquik endpoint to call. Pick the matching job, then open the API page for params, responses, and examples. * **One tweet by ID:** `GET /x/tweets/{id}`. Handoff: tweet row with media and metrics. * **Many known tweet IDs:** `GET /x/tweets`. Handoff: Batch response before single-tweet loops. * **Keyword or advanced search:** `GET /x/tweets/search`. Handoff: cursor pages, or `tweet_search_extractor` exports. * **Profile timeline:** `GET /x/users/{id}/tweets`. Handoff: `cursor`, `includeReplies`, and `includeParentTweet`. * **Article body:** `GET /x/articles/{tweetId}`. Handoff: body blocks, cover image, author, and not-found handling. * **Replies, quotes, or threads:** `GET /x/tweets/{id}/replies`. Handoff: Conversation rows from replies, quotes, or threads. * **Follower or following page:** `GET /x/users/{id}/followers` or `GET /x/users/{id}/following`. Handoff: `users`, `has_next_page`, and `next_cursor`. * **Follower or following export:** `POST /extractions/estimate`. Handoff: `follower_explorer` or `following_explorer` estimate, job, CSV, JSON, or XLSX. * **Campaign verification:** `GET /x/followers/check`, retweeters, replies, quotes, or draws. Handoff: proof exports. * **Post tweet or reply:** `POST /x/tweets`. Handoff: `tweetId`, write status, credits, and public media URLs. * **Direct messages:** `GET /x/dm/{userId}/history`. Handoff: history, cursor state, outbound `messageId`, and success. * **1-second monitoring:** `POST /monitors` or `POST /monitors/keywords`. Handoff: Signed payloads and delivery IDs. * **AI agent handoff:** `xquik.request(...)`. Handoff: normalized Xquik API results for MCP clients. * **Saved file exports:** `GET /extractions/{id}/export`. Handoff: CSV, JSON, XLSX, Markdown, PDF, or TXT. ## Integration Handoff Matrix Use this matrix before choosing code, webhooks, MCP, or exports. Setup: API key, account username or keyword query, and event types. First call: `POST /monitors` for accounts or `POST /monitors/keywords` for search queries, then `GET /events`. Returns: tweet, reply, quote, and retweet events. Handoff: polling cursor or signed webhook payload. Cost: active monitors bill 21 credits per hour while enabled. Setup: webhook URL and subscribed event types. First call: `POST /webhooks`, then `POST /webhooks/{id}/test`. Returns: delivery status and signed event body. Handoff: queue, Slack, CRM, or warehouse endpoint. Cost: webhook management and deliveries are included with active monitor billing. Setup: MCP API key or OAuth. First call: `xquik.request('/api/v1/x/tweets/search')`. Returns: tweets, profiles, and monitor events. Handoff: MCP tool result with normalized pagination. Cost: Tweet search, profile lookup, and follower exports use endpoint credits. Setup: target username, `follower_explorer` or `following_explorer`, and optional `resultsLimit`. First call: `POST /extractions/estimate`, then `POST /extractions`. Returns: job status, rows, and export links. Handoff: CSV, JSON, XLSX, or paginated JSON. Cost: estimate before creating the job. Setup: tweet ID and optional `resultsLimit`. First call: `POST /extractions/estimate` with `reply_extractor`. Returns: reply author fields, reply tweet fields, engagement counts, and metadata. Handoff: CSV, JSON, XLSX, JSONL, or moderation queue rows. Cost: 1 credit per reply returned or extracted. Setup: connected X account plus text, media URLs, or parent tweet ID. First call: `POST /x/tweets`. Returns: `tweetId`, `success`, `charged`, and `chargedCredits`. Handoff: published tweet ID and charged credits for queue, CRM, CMS, or agent state. Cost: 30 credits text-only, plus 2 credits per started MB across attached media. Setup: topic, goal, tone, and optional style username. First call: `POST /compose` with `step`. Returns: algorithm guidance, refined draft, and score checklist. Handoff: draft text or X compose URL. Cost: Compose, refine, and score are free. ## High-Value Workflows First Prioritize rows for analysts, IDs for systems of record, events for queues, and published action IDs for audit trails. Turns search intent into research, lead, support, and AI retrieval rows. First: `POST /extractions/estimate` with `tweet_search_extractor`. Handoff: export file, paginated JSON, or direct `GET /x/tweets/search` page. Cost model: 1 credit per tweet returned or extracted. Turns a post's conversation into moderation, support, giveaway, research, or AI rows. First: `POST /extractions/estimate` with `reply_extractor` and `targetTweetId`. Handoff: export file, paginated JSON, JSONL rows, or direct `GET /x/tweets/{id}/replies` page. Cost model: 1 credit per reply returned or extracted. Builds audience and interest tables with stable X user IDs for import and upsert. First: `POST /extractions/estimate` with `follower_explorer` or `following_explorer`. Handoff: CSV, JSON, XLSX, or CRM field map keyed by `x_user_id`. Cost model: 1 credit per user returned. Delivers new posts, replies, quotes, and retweets to queues without polling. First: `POST /monitors` for accounts or `POST /monitors/keywords` for search queries, then `POST /webhooks`. Handoff: signed payload with `deliveryId`, `streamEventId`, `eventType`, and tweet data. Cost model: 21 credits per active monitor-hour; webhook delivery is included. Publishes media tweets or replies from public image URLs or 1 public MP4 URL up to 100 MB. First: `POST /x/tweets` with `media`. Handoff: `tweetId`, `success`, `chargedCredits`, original `media` URLs, and optional `reply_to_tweet_id`. Cost model: 30 credits text-only, plus 2 credits per started MB across attached media. Lets support, sales, and agents store the outbound message ID. First: `GET /x/users/{id}` if needed; use `GET /x/dm/{userId}/history?account=...`, then `POST /x/dm/{userId}`. Handoff: `account`, `messages`, `has_next_page`, `next_cursor`, `messageId`, and `success`. Cost model: 1 credit per user lookup or history message; 10 credits per DM send. ### 1. Scrape tweets to CSV, JSON, or XLSX Use this to scrape tweets, search tweets, export hashtag results, or hand matching posts to analysts. Estimate first, create the job, poll it, then export the format your system expects. For reply-specific exports, use the next workflow. ```bash theme={null} 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": "tweet_search_extractor", "searchQuery": "from:username webhook OR SDK", "language": "en", "resultsLimit": 500 }' | jq ``` The extraction returns a job ID. `GET /extractions/{id}` returns `job`, `results`, `hasMore`, and `nextCursor`; `GET /extractions/{id}/export?format=csv` downloads the rows. For live pagination without a stored job, call `GET /x/tweets/search` with `q`; leave `limit` unset for a simple cursor loop. For bounded pulls, keep the same `q`, filters, and `limit` while sending `next_cursor` as `cursor`. It returns `tweets`, `has_next_page`, and `next_cursor`. ### 2. Scrape tweet replies to CSV, JSON, or XLSX Use this when a moderation, support, giveaway, research, or AI review system needs every reply under a post as rows. Estimate with `reply_extractor` and `targetTweetId`, create the job, poll it, then export `format=csv`, `format=json`, or `format=xlsx`. ```bash theme={null} 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", "resultsLimit": 500 }' | jq ``` `GET /extractions/{id}` returns reply rows, `hasMore`, and `nextCursor`; pass `nextCursor` as `cursor` for more stored results. For live pagination without a stored job, call `GET /x/tweets/{id}/replies`, pass `next_cursor` back as `cursor`, and store `tweets`, `has_next_page`, and `next_cursor`. ### 3. Export followers or following to CRM Use this for follower export, following export, audience ownership, CRM import, or warehouse sync. Keep `resultsLimit` on estimate and create calls for a predictable credit cap. ```bash theme={null} curl -X POST https://xquik.com/api/v1/extractions \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "toolType": "follower_explorer", "targetUsername": "username", "resultsLimit": 10000 }' | jq ``` Use `following_explorer` with the same `targetUsername` shape when the job needs accounts the user follows. After `status` becomes `completed`, export `format=csv`, `format=xlsx`, or `format=json`. Map stable `User ID` values to `x_user_id`; do not key imports by display name. ### 4. Monitor tweets to signed webhooks Use this when a workflow needs account alerts, keyword alerts, support routing, warehouse ingest, or queue fanout within seconds. Create an account monitor or keyword monitor, register the webhook URL, test delivery, then process signed events asynchronously. ```bash theme={null} curl -X POST https://xquik.com/api/v1/monitors \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "username": "username", "eventTypes": ["tweet.new", "tweet.reply", "tweet.quote"] }' | jq ``` For query alerts, call `POST /monitors/keywords` with `query` and `eventTypes` instead of `username`. Account and keyword monitors both check every 1 second while active, and both can deliver events to the same signed webhooks. Each POST has 1 event; multiple matches produce multiple POSTs. Payloads include durable IDs: persist `deliveryId` for delivery-level idempotency and `streamEventId` when the same monitor event must be processed once across retries or endpoints. Store the one-time `secret` returned by `POST /webhooks` and verify `X-Xquik-Signature` against the raw body before accepting the event. Return `2xx` before slow CRM, Slack, warehouse, or queue work starts. #### Receiver storage handoff For Zapier, Make, and Pipedream receivers, map production payload IDs before enqueueing slow work: ```json theme={null} { "record_type": "workflow_webhook_receiver_parity", "delivery_id": "502", "stream_event_id": "9002", "event_type": "tweet.new", "occurred_at": "2026-02-24T14:22:00.000Z", "duplicate_delivery_status": "2xx", "duplicate_event_status": "2xx", "event_join": "GET /api/v1/events/9002", "shared_storage_excludes": [ "endpoint_signing_values", "raw_request_body", "raw_signature", "full_headers" ] } ``` Return `2xx` for duplicate `deliveryId` or `streamEventId` after signature verification. Store raw request bytes only long enough to verify `X-Xquik-Signature`; do not put endpoint signing values, raw body, raw signature, or full headers in shared workflow rows. ### 5. Post media tweets or replies Use this when an agent or app has public image URLs or exactly 1 public MP4 video URL up to 100 MB and needs to post a tweet or reply. Pass those URLs directly in the `media` array on `POST /x/tweets`. Do not call `POST /x/media` first for tweet posts when the media is already public; `POST /x/tweets` rejects `media_ids` with `400 unsupported_field`. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/tweets \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "brand_account", "text": "Product screenshot from today", "reply_to_tweet_id": "1893456789012345678", "media": ["https://example.com/product-screenshot.png"] }' | jq ``` The response is a durable action record. Store `id`, `status`, `billing`, `result`, `request.hash`, and `statusUrl`. Poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new `Idempotency-Key`. Store media URLs with the action record. Text-only tweet or reply writes cost 30 credits; attached media adds 2 credits per started MB across all files. Use `POST /x/media` only when you need a one-item `media_ids` array for [`POST /x/dm/{userId}`](/api-reference/x-write/send-dm). ### 6. Send direct messages with returned IDs Use this when a support, sales, or agent workflow needs direct messages with audit trails. Look up the recipient user ID, pass the same connected sender as `account` for history reads and DM writes, then store returned message IDs. ```bash theme={null} curl -G https://xquik.com/api/v1/x/dm/987654321/history \ --data-urlencode "account=brand_account" \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` History reads require `account`, and that connected account must participate in the conversation. Missing `account` returns `400 account_required`; a non-participant account returns `403 dm_not_permitted`. ```bash theme={null} curl -X POST https://xquik.com/api/v1/x/dm/987654321 \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "account": "brand_account", "text": "Thanks for reaching out. Here is the next step." }' | jq ``` `GET /x/dm/{userId}/history` returns `messages`, `has_next_page`, and `next_cursor`; pass `next_cursor` back as `cursor` for older messages. `POST /x/dm/{userId}` returns `messageId` and `success`, which you should store on the support ticket, CRM note, or agent state. ## Focused Workflow Pages Use the overview to choose the path, then move to the focused page for copy-ready examples, SDK handoff, and endpoint-specific error recovery. Build CSV, JSON, or XLSX exports from `tweet_search_extractor`, or use direct `GET /x/tweets/search` pagination. Turn a post conversation into `reply_extractor` jobs, direct replies pagination, JSONL rows, or moderation queues. Estimate `follower_explorer`, export CSV/JSON/XLSX files, and map stable `x_user_id` fields. Check social actions and draw exports. Test signed deliveries, verify `X-Xquik-Signature`, store `deliveryId` and `streamEventId`, and return `2xx` before slow work. Use public URLs in tweet `media`; upload only when DMs need one `media_ids` item. Read DM history with `account`, send DMs, and store returned `messageId` values. Connect agents, call `xquik.request(...)`, and hand normalized pagination back to memory. Use `POST /compose` for compose, refine, and score loops without usage credits. ## Public draw results Completed giveaway draws have a public results page at `https://xquik.com/results/{drawId}`. No authentication required. Share the URL with participants for transparency. Start 1-second checks for one account. Receive detected events through signed webhooks. Connection details and setup instructions. Verify webhook payloads with HMAC-SHA256. # Twitter API Rate Limits, Errors & Webhook Events Source: https://docs.xquik.com/guides/x-api-integration-checklist Handle Xquik REST API calls with authentication, pagination, HTTP errors, Twitter API rate limits, webhook events, durable writes, and normalized responses.
For the complete documentation index, see llms.txt.
Use this production checklist to handle Twitter API rate limits, REST API calls, error responses, cursor pagination, and signed webhook events with Xquik. Apply its API error handling rules before launching a client, workflow, or agent. Use API pagination for complete tweet, follower, reply, and event collections. Treat HTTP status codes as control flow. Protect durable API writes with idempotency keys and status polling. ## Integration Readiness Checklist Run this checklist before connecting a production client, workflow builder, or agent to the API. Send a full account key or OAuth token for account operations. Use an active guest key on its 33 paid reads. Use direct MPP only on its 7 fixed-price operations. Handle the documented `401` and `402` contracts separately. Default REST examples use the v1 shape. Send `xquik-api-contract: 2026-04-29` when your client expects snake\_case, date-time fields as Unix seconds, structured errors, `has_more`, and `next_cursor`. The API MCP server sends this contract automatically. Use `cursor` with `nextCursor` on events, draws, and extractions. Use `after` on Radar. Use `afterCursor` on drafts. Use `cursor` with `next_cursor` on X endpoints. De-duplicate stable IDs and reject repeated cursors. Handle `402 no_credits` and `402 insufficient_credits`. Treat every `payment_options` action as an offer. Ask the user to select an amount and explicitly confirm before creating checkout. Respect `Retry-After`. Read calls allow 300 per 1s. Writes allow 120 per 60s. Deletes allow 60 per 60s. Back off after repeated `429 rate_limit_exceeded` responses. Send a unique `Idempotency-Key`. Store the returned action. Poll `statusUrl` while `terminal` is `false`. Retry only when `safeToRetry` is `true`, using a new key. ## Validate Every HTTP Request Match each HTTP request to its documented API endpoint. Use the listed HTTP methods, path parameters, query parameters, and headers. Send `GET` for reads. Use `POST` requests for creation and actions. Use `PATCH` or `PUT` for supported updates. Send `DELETE` only for documented removal routes. Validate required values before sending the REST API call. Confirm X usernames, tweet IDs, list IDs, community IDs, and monitor IDs. Check numeric limits and allowed enum values. Reject an empty search query on the client side. These checks reduce avoidable `400 Bad Request` responses. Send JSON writes with `Content-Type: application/json`. Encode the request body once. Never send secrets in query parameters. Put account keys in `x-api-key` or `Authorization`. Store webhook secrets outside the request body. Check the complete URL before release. The base URL is `https://xquik.com/api/v1`. Keep the version segment in every API endpoint. Do not send production credentials to test hosts. ## Classify API Responses Read the HTTP status code before parsing API responses. A successful response does not always use `200`. Creation routes can return `201`. Accepted work can return `202`. Durable actions can return a status URL for later polling. Handle each failure class separately: * `400` means the path, query, or request body failed validation. * `401` means authentication is missing or invalid. * `402` means billing access or credits need attention. * `404` means the requested tweet, profile, monitor, or resource is unavailable. * `424` or `502` means a read dependency failed temporarily. * `429` means the current method bucket reached its limit. Keep the machine-readable error code with each failed request. Show human-readable error messages where a person must act. Never replace exact codes with guessed messages. When an error occurs, inspect the code before choosing a retry. ## Separate Client and Server Errors Fix client-side errors before retrying. A repeated invalid query produces the same response. Correct the field, type, identifier, or authentication header. Do not hide validation failures behind automatic retries. Treat temporary server errors differently. Retry `424` and `502` failures with bounded exponential backoff. Stop after the configured attempt limit. Surface the final failure with its original status and error code. Never retry every `4xx` response. A `401` needs a valid credential. A `402` needs an explicit billing choice. A `404` may require a different tweet or profile ID. A `429` must wait for `Retry-After`. Preserve request context without logging secrets. Record the method, route, status, retry count, and returned code. Remove API keys, bearer tokens, webhook secrets, cookies, and payment credentials. ## Page Through Tweets, Followers & Replies API pagination uses opaque cursors. Store each cursor exactly as returned. Never decode, trim, or construct it. Use stable tweet and profile IDs to remove duplicates across pages. Tweet, profile, follower, reply, timeline, community, and list reads use `cursor`. Send the returned `next_cursor` on the next REST API call. Continue while `has_next_page` is true. Events, draws, and extractions use `cursor`. Radar uses `after`. Drafts use `afterCursor`. Send the matching `nextCursor` value without changing its case. Each family has a different parameter contract. Continue through an empty page when the response still reports more results. Stop when the next cursor is missing, unchanged, or already seen. Report that edge case instead of looping forever. Store partial tweets, followers, following profiles, replies, or events before requesting the next page. Resume from the last confirmed cursor after a temporary failure. ## Recover From Twitter API Rate Limits Treat Twitter API rate limits as method-specific Xquik buckets. Reads share one window. Writes share another. Deletes use their own window. One busy workflow should not force unrelated methods into the same retry queue. Always read `Retry-After` from a `429` response. Wait for that duration before sending another HTTP request. Add bounded jitter when many workers share one credential. This prevents synchronized retry spikes. Limit concurrency before the error occurs. Batch tweet or profile IDs on endpoints that support batches. Use exports for large follower, reply, or timeline jobs. Cache stable profile fields when freshness permits. Track `rate_limit_exceeded` by method and route. Measure repeated throttles, retry delay, and final success. Reduce request frequency when the same route reaches its bucket repeatedly. Never treat a `429` as an authentication or billing failure. Keep its recovery path separate from `401` and `402` handling. ## Process Webhook Events Safely Accept webhook events only on HTTPS. Save the one-time secret during webhook creation. Verify the HMAC signature before trusting a tweet or profile event. Reject a missing or invalid signature. Read the event type before processing the payload. Tweet events include new tweets, replies, quotes, reposts, media, links, polls, mentions, hashtags, and long-form posts. Profile events cover names, usernames, bios, locations, URLs, avatars, banners, verification, protection, pinned tweets, and availability. Store the event ID before starting side effects. Use it to prevent duplicate notifications or repeated downstream writes. Keep handlers idempotent across delivery retries. Return a successful status only after accepting the event. Keep expensive work outside the request path. Test signatures, failure responses, and retries before enabling real-time monitor traffic. Use [webhook verification](/webhooks/verification) for signature code. Use [webhook testing](/guides/twitter-webhook-testing) for delivery and retry checks. ## Protect Durable API Writes Send a unique `Idempotency-Key` with each new write intent. Keep that key with the submitted action. Do not reuse it for a different tweet, like, follow, message, or profile update. Store the returned action ID and `statusUrl`. Poll while `terminal` is false. Stop when the action succeeds, fails, or requires user action. Respect the documented polling interval and rate limits. Read `safeToRetry` before creating another action. Retry only when it is true. Use a new idempotency key for that retry. Preserve the earlier action for diagnosis. Treat connected-account failures as account state. Reauthenticate the X account when the response requires it. Do not convert an account restriction into an automatic retry loop. Verify the final action result before updating local state. A submitted request does not prove that X accepted the write. ## Test Production Edge Cases Test success and failure paths before launch. Use a valid API key, then an invalid key. Send one malformed request body. Request one missing tweet or profile. Exhaust a safe test rate-limit window. Confirm each response maps to the expected handler. Test API pagination with multiple pages, an empty intermediate page, and a repeated cursor. Confirm the collector stops safely. Verify deduplication with repeated tweet or profile IDs. Test webhook events with a valid signature, invalid signature, duplicate event, and handler failure. Confirm only verified events reach downstream systems. Test durable writes through submission, polling, success, and failure. Confirm the client never repeats an unsafe action. Review logs to ensure no credential or webhook secret appears. Keep these checks in automated integration tests. Run them after authentication, pagination, response, webhook, or write-contract changes. ## Conventions Treat every ID as an opaque string. IDs may use digits, UUIDs, or prefixes. Never parse them as numbers: ```json theme={null} { "id": "123456789" } ``` Default REST responses use ISO 8601 UTC strings. The normalized contract converts date-time fields to Unix seconds and renames `createdAt` to `created`: ```json theme={null} { "created": 1771929000 } ``` Default REST errors return an `error` field with a machine-readable string code: ```json theme={null} { "error": "error_code" } ``` **Common errors:** `invalid_input` means the request body, query, or path failed validation. Fix the schema, field types, or required parameters before retrying. `unauthenticated` means the API key or bearer token is missing or invalid. Send `x-api-key` or a valid `Authorization` bearer token. `no_subscription`, `no_credits`, and `insufficient_credits` mean the request lacks usable billing access or enough credits. Existing available credits work without an active plan. `not_found` means the requested resource does not exist or is not available to the authenticated account. `rate_limit_exceeded` includes `Retry-After` and JSON `retryAfter`. Wait for the window and retry with backoff. `x_api_unavailable` means the read service is temporarily unavailable. Retry with exponential backoff. See [Error Handling](/guides/error-handling) for the full error code list and handling strategies. Events, draws, extractions, Radar, and drafts use cursor-based pagination. More results return `hasMore: true` and a `nextCursor` value: ```json theme={null} { "events": [], "hasMore": true, "nextCursor": "MjAyNi0wMi0yNFQxMDozMDowMC4wMDBa..." } ``` Pass `nextCursor` as `cursor` for events, draws, and extractions. Pass it as `after` for Radar. Pass it as `afterCursor` for drafts: ```bash theme={null} curl "https://xquik.com/api/v1/events?cursor=MjAyNi0wMi0yNFQxMDozMDowMC4wMDBa..." \ -H "x-api-key: xq_your_api_key_here" ``` Tweet, profile, follower, reply, timeline, community, and list endpoints under `/x/*` use a different pagination shape. When more results exist, the response includes `has_next_page: true` and a `next_cursor` value: ```json theme={null} { "tweets": [], "has_next_page": true, "next_cursor": "DAABCgABGRnYttk__..." } ``` Pass `next_cursor` as the `cursor` query parameter to fetch the next page: ```bash theme={null} curl "https://xquik.com/api/v1/x/tweets/search?q=xquik&cursor=DAABCgABGRnYttk__..." \ -H "x-api-key: xq_your_api_key_here" ``` Cursors are opaque strings. Do not decode or construct them. Continue through empty pages while the response says more data exists. Stop and surface a pagination error when the next cursor is missing, unchanged, or already seen. Monitors, webhooks, and API keys return up to 200 items without pagination. v1 keeps the default response contract unchanged for existing clients. Send `xquik-api-contract: 2026-04-29` to opt in to the normalized v1 response contract. ```bash theme={null} curl "https://xquik.com/api/v1/events" \ -H "x-api-key: xq_your_api_key_here" \ -H "xquik-api-contract: 2026-04-29" ``` Opt-in responses use snake\_case field names, Unix timestamps in seconds, structured error objects, `has_more` and `next_cursor` pagination fields, an `object` field on recognized resources, and prefixed IDs where available. Date-only strings stay unchanged. A default `createdAt` field becomes `created`. ```json theme={null} { "events": [], "has_more": true, "next_cursor": "MjAyNi0wMi0yNFQxMDozMDowMC4wMDBa..." } ``` Legacy dependency failures that return `502` by default return `424 Failed Dependency` in the opt-in contract: ```json theme={null} { "error": { "type": "dependency_error", "code": "x_api_unavailable", "message": "Read service temporarily unavailable. Retry shortly." } } ``` Pass `next_cursor` as `cursor` on tweet, profile, follower, reply, timeline, community, and list endpoints. Use `cursor` for events, draws, and extractions. Use `after` for Radar. Use `afterCursor` for drafts. ## Event Types ### Monitor Events Monitors, webhooks, and events share these event types: * Tweet events: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, and `tweet.longform`. * Profile identity events: `profile.name.changed`, `profile.username.changed`, `profile.bio.changed`, `profile.location.changed`, and `profile.url.changed`. * Profile media events: `profile.avatar.changed` and `profile.banner.changed`. * Profile status events: `profile.verified.changed`, `profile.protected.changed`, `profile.pinned_tweet.changed`, and `profile.unavailable.changed`. Keyword monitors accept only the 10 `tweet.*` types above. Original tweet posted by the monitored account or matching query. Used when no reply, quote, or retweet signal is present. Quote tweet posted by the monitored account or matching query. Classified when quote metadata is present. Reply posted by the monitored account or matching query. Classified from reply flags or reply target IDs. Retweet posted by the monitored account or matching query. Classified from retweet flags or `RT @` text. # TypeScript Types for Tweets, Followers & X API Source: https://docs.xquik.com/guides/x-api-typescript-types Use TypeScript definitions for tweets, profiles, followers, monitors, webhooks, extractions, write actions, pagination, and API errors. Follow exact steps.
For the complete documentation index, see llms.txt.
Copy-pasteable TypeScript helpers for common Xquik API objects. Use these for prototypes, docs examples, and lightweight clients. For complete generated coverage, use the [OpenAPI spec](https://docs.xquik.com/openapi.yaml) or an [official SDK](/sdks). | Contract area | Curated types | Route use | | ------------- | ----------------------------------------------- | ----------------------------------------------------------- | | Tweets | `Tweet`, `PaginatedTweets`, `TweetSearchParams` | Search tweets and continue with `nextCursor`. | | Profiles | `User`, `PaginatedUsers` | Read profiles, followers, following, and mentions. | | Monitors | `Monitor`, `KeywordMonitor` | Create and reconcile account or keyword monitors. | | Events | `Event`, `EventList` | Page through stored monitor events. | | Webhooks | `Webhook`, `WebhookDelivery` | Track subscriptions and delivery attempts. | | Extractions | `Extraction`, `ExtractionResultPage` | Run follower or reply exports and retrieve saved rows. | | Write actions | `WriteAction` | Track tweets, follows, likes, reposts, and profile updates. | | Errors | `ApiError` | Branch on status, code, message, and retry details. | ## Usage 3 ways to use these types in your project: 1. **Copy curated types** - copy the "Response types", "Request types", and "Shared types" tabs into a single `xquik-types.ts` file 2. **Copy individual interfaces** - grab only the types you need (e.g. `Tweet`, `EventList`) 3. **Generate full coverage from OpenAPI** - use the [OpenAPI spec](https://docs.xquik.com/openapi.yaml) with tools like `openapi-typescript` to auto-generate types: ```bash theme={null} npx openapi-typescript https://docs.xquik.com/openapi.yaml -o xquik-api.d.ts ``` For production projects, option 3 or an official SDK is the safest source for complete coverage. For quick prototypes, copy only the interfaces you need. Response objects returned by API endpoints. ```typescript theme={null} // ─── Account ───────────────────────────────────────────── interface Account { plan: "active" | "inactive"; monitorsAllowed: number; monitorsUsed: number; creditInfo?: { balance: number; lifetimePurchased: number; lifetimeUsed: number; autoTopupEnabled: boolean; }; xUsername?: string; } // ─── API Keys ──────────────────────────────────────────── // Returned when creating a new key (includes full key) interface ApiKeyCreated { id: string; fullKey: string; prefix: string; name: string; createdAt: string; } // Returned when listing keys (full key is never exposed) interface ApiKey { id: string; name: string; prefix: string; isActive: boolean; createdAt: string; lastUsedAt?: string; } // ─── Monitors ──────────────────────────────────────────── interface Monitor { id: string; username: string; xUserId: string; eventTypes: EventType[]; isActive: boolean; createdAt: string; nextBillingAt: string; } interface KeywordMonitor { id: string; query: string; eventTypes: EventType[]; isActive: boolean; createdAt: string; nextBillingAt: string; } // ─── Events ────────────────────────────────────────────── interface Event { id: string; type: EventType; monitorId: string; monitorType: "account" | "keyword"; occurredAt: string; data: EventData; username?: string; query?: string; keywordMonitorId?: string; xEventId?: string; } interface EventList { events: Event[]; hasMore: boolean; nextCursor?: string; } // ─── Webhooks ──────────────────────────────────────────── // Returned when creating a webhook (includes signing secret) interface WebhookCreated { id: string; url: string; eventTypes: EventType[]; secret: string; createdAt: string; } // Returned when listing webhooks (secret is never exposed) interface Webhook { id: string; url: string; eventTypes: EventType[]; isActive: boolean; createdAt: string; } // ─── Deliveries ────────────────────────────────────────── interface Delivery { id: string; streamEventId: string; status: "pending" | "delivered" | "failed" | "exhausted"; attempts: number; lastStatusCode?: number; lastError?: string; createdAt: string; deliveredAt?: string; } // ─── Webhook Payload ───────────────────────────────────── interface WebhookPayload { eventType: EventType; schemaVersion: 1; deliveryId: string; streamEventId: string; occurredAt: string; data: EventData; username?: string; query?: string; } interface WebhookTestPayload { eventType: "webhook.test"; data: { message: string }; timestamp: string; } // ─── Draws ────────────────────────────────────────────── interface Draw { id: string; tweetId: string; tweetUrl: string; tweetText: string; tweetAuthorUsername: string; tweetLikeCount: number; tweetRetweetCount: number; tweetReplyCount: number; tweetQuoteCount: number; status: "pending" | "running" | "completed" | "failed"; totalEntries: number; validEntries: number; createdAt: string; drawnAt?: string; } interface DrawListItem { id: string; tweetUrl: string; status: "pending" | "running" | "completed" | "failed"; totalEntries: number; validEntries: number; createdAt: string; drawnAt?: string; } interface DrawWinner { position: number; authorUsername: string; tweetId: string; isBackup: boolean; } interface DrawList { draws: DrawListItem[]; hasMore: boolean; nextCursor?: string; } // ─── Extractions ──────────────────────────────────────── interface ExtractionJob { id: string; toolType: ExtractionToolType; status: "pending" | "running" | "completed" | "failed"; totalResults: number; targetTweetId?: string; targetUsername?: string; targetUserId?: string; targetCommunityId?: string; searchQuery?: string; errorMessage?: string; createdAt: string; completedAt?: string; } interface ExtractionResult { id: string; xUserId: string; xUsername?: string; xDisplayName?: string; xFollowersCount?: number; xVerified?: boolean; xProfileImageUrl?: string; tweetId?: string; tweetText?: string; tweetCreatedAt?: string; createdAt: string; enrichmentData?: Record; } interface ExtractionList { extractions: ExtractionJob[]; hasMore: boolean; nextCursor?: string; } interface ExtractionEstimate { allowed: boolean; source: "replyCount" | "retweetCount" | "quoteCount" | "followers" | "resultsLimit" | "unknown"; estimatedResults: number; creditsRequired: string; creditsAvailable: string; resolvedXUserId?: string; } // ─── X API ────────────────────────────────────────────── interface Tweet { id: string; text: string; type?: string; createdAt?: string; retweetCount?: number; replyCount?: number; likeCount?: number; quoteCount?: number; viewCount?: number; bookmarkCount?: number; media?: TweetMediaItem[]; url?: string; lang?: string; isReply?: boolean; inReplyToId?: string; inReplyToUserId?: string; inReplyToUsername?: string; conversationId?: string; source?: string; displayTextRange?: number[]; isNoteTweet?: boolean; isQuoteStatus?: boolean; isLimitedReply?: boolean; entities?: Record; author?: UserProfile; quoted_tweet?: Tweet; retweeted_tweet?: Tweet; } interface TweetAuthor { id: string; username: string; followers: number; verified: boolean; profilePicture?: string; } interface TweetSearchResult { id: string; text: string; createdAt: string; likeCount: number; retweetCount: number; replyCount: number; author: { id: string; username: string; name: string; verified: boolean; }; media?: TweetMediaItem[]; } interface UserProfile { id: string; username: string; name: string; description?: string; followers?: number; following?: number; verified?: boolean; profilePicture?: string; coverPicture?: string; location?: string; createdAt?: string; statusesCount?: number; mediaCount?: number; url?: string; favouritesCount?: number; hasCustomTimelines?: boolean; isTranslator?: boolean; withheldInCountries?: string[]; possiblySensitive?: boolean; pinnedTweetIds?: string[]; isAutomated?: boolean; automatedBy?: string; unavailable?: boolean; unavailableReason?: string; verifiedType?: string; profile_bio?: Record; } interface FollowerCheck { sourceUsername: string; targetUsername: string; isFollowing: boolean; isFollowedBy: boolean; } // ─── Trends ───────────────────────────────────────────── interface Trend { name: string; description?: string; rank?: number; query?: string; } interface TrendList { trends: Trend[]; total: number; woeid: number; } // ─── Radar ────────────────────────────────────────────── interface RadarItem { id: string; title: string; description?: string; url?: string; imageUrl?: string; source: RadarSource; sourceId: string; category: RadarCategory; region: string; language: string; score: number; metadata: Record; publishedAt: string; createdAt: string; } interface RadarList { items: RadarItem[]; hasMore: boolean; nextCursor?: string; } // ─── Styles ───────────────────────────────────────────── interface StyleProfile { xUsername: string; isOwnAccount: boolean; tweetCount: number; fetchedAt: string; tweets: StyleTweet[]; } interface StyleListItem { xUsername: string; isOwnAccount: boolean; tweetCount: number; fetchedAt: string; } interface StyleList { styles: StyleListItem[]; } interface StyleComparison { style1: StyleProfile; style2: StyleProfile; } interface PerformanceAnalysis { xUsername: string; tweetCount: number; tweets: PerformanceTweet[]; } // ─── Drafts ───────────────────────────────────────────── interface Draft { id: string; text: string; topic?: string; goal?: "engagement" | "followers" | "authority" | "conversation"; createdAt: string; updatedAt: string; } interface DraftList { drafts: Draft[]; hasMore: boolean; nextCursor?: string; } // ─── X Accounts ─────────────────────────────────────────── interface XAccount { id: string; username: string; displayName: string; isActive: boolean; createdAt: string; } // ─── Compose ────────────────────────────────────────────── interface RadarRecommendation { endpoint: string; guidance: string; source: RadarSource; useFor: string; } // step="compose" response interface ComposeTweetResult { contentRules: { rule: string }[]; engagementMultipliers: { action: string; multiplier: string }[]; engagementVelocity: string; followUpQuestions: string[]; intentUrl: string; nextStep: string; radarRecommendations: RadarRecommendation[]; savedStyles?: { tweetCount: number; username: string }[]; scorerWeights: { signal: string; weight: null; context: string }[]; source: string; styleNote?: string; styleTweets?: string[]; topPenalties: string[]; } // step="refine" response interface RefineTweetResult { compositionGuidance: string[]; examplePatterns: { description: string; pattern: string }[]; intentUrl: string; nextStep: string; } // step="score" response interface ScoreTweetResult { passed: boolean; passedCount: number; totalChecks: number; topSuggestion: string; nextStep: string; checklist: { factor: string; passed: boolean; suggestion?: string }[]; intentUrl?: string; } // ─── X Write ────────────────────────────────────────────── interface XWriteResponse { success: boolean; data?: Record; } // ─── Support Tickets ────────────────────────────────────── interface SupportTicket { publicId: string; subject: string; status: "open" | "in_progress" | "resolved" | "closed"; messageCount: number; createdAt: string; updatedAt: string; } interface SupportTicketDetail { publicId: string; subject: string; status: "open" | "in_progress" | "resolved" | "closed"; createdAt: string; updatedAt: string; messages: SupportMessage[]; } interface SupportMessage { body: string; sender: "user" | "support"; createdAt: string; } // ─── Error Response ────────────────────────────────────── type ApiErrorType = | "api_error" | "authentication_error" | "billing_error" | "dependency_error" | "invalid_request_error" | "permission_error" | "rate_limit_error"; interface StructuredApiError { message: string; type: ApiErrorType; code: string; } interface ApiError { error: string | StructuredApiError; message?: string; reason?: string; retryAfter?: number; retryAfterMs?: number; } ``` Request body types for POST/PATCH endpoints. ```typescript theme={null} interface CreateMonitorRequest { username: string; eventTypes: EventType[]; } interface UpdateMonitorRequest { eventTypes?: EventType[]; isActive?: boolean; } interface CreateWebhookRequest { url: string; eventTypes: EventType[]; } interface UpdateWebhookRequest { url?: string; eventTypes?: EventType[]; isActive?: boolean; } interface CreateApiKeyRequest { name?: string; } interface CreateDrawRequest { tweetUrl: string; winnerCount?: number; backupCount?: number; uniqueAuthorsOnly?: boolean; mustRetweet?: boolean; mustFollowUsername?: string; filterMinFollowers?: number; filterAccountAgeDays?: number; filterLanguage?: string; requiredKeywords?: string[]; requiredHashtags?: string[]; requiredMentions?: string[]; } interface CreateExtractionRequest { toolType: ExtractionToolType; targetTweetId?: string; targetUsername?: string; targetCommunityId?: string; targetListId?: string; targetSpaceId?: string; searchQuery?: string; resultsLimit?: number; } interface CreateDraftRequest { text: string; topic?: string; goal?: "engagement" | "followers" | "authority" | "conversation"; } interface ComposeRequest { step: "compose" | "refine" | "score"; topic?: string; goal?: ComposeTweetGoal; styleUsername?: string; tone?: string; additionalContext?: string; callToAction?: string; hasLink?: boolean; /** @deprecated Accepted for compatibility. Text checks ignore it. */ hasMedia?: boolean; mediaType?: MediaType; draft?: string; } ``` Enums and shared types used across request and response objects. ```typescript theme={null} type EventType = | "tweet.new" | "tweet.quote" | "tweet.reply" | "tweet.retweet" | "tweet.media" | "tweet.link" | "tweet.poll" | "tweet.mention" | "tweet.hashtag" | "tweet.longform" | "profile.avatar.changed" | "profile.banner.changed" | "profile.name.changed" | "profile.username.changed" | "profile.bio.changed" | "profile.location.changed" | "profile.url.changed" | "profile.verified.changed" | "profile.protected.changed" | "profile.pinned_tweet.changed" | "profile.unavailable.changed"; // The data field contains the tweet object stored with the monitor event. interface TweetEventData { id: string; text: string; author: { id: string; userName: string; name: string; }; isRetweet: boolean; isReply: boolean; isQuote: boolean; createdAt: string; inReplyToId?: string; quoted_tweet?: { id: string; text: string; author: { userName: string }; }; } type EventData = TweetEventData; interface EventFilters { keywords?: { include?: string[]; exclude?: string[] }; minLikes?: number; minRetweets?: number; verifiedOnly?: boolean; } type ExtractionToolType = | "article_extractor" | "community_extractor" | "community_moderator_explorer" | "community_post_extractor" | "community_search" | "favoriters" | "follower_explorer" | "following_explorer" | "list_follower_explorer" | "list_member_extractor" | "list_post_extractor" | "mention_extractor" | "people_search" | "post_extractor" | "quote_extractor" | "reply_extractor" | "repost_extractor" | "space_explorer" | "thread_extractor" | "tweet_search_extractor" | "user_likes" | "user_media" | "verified_follower_explorer"; type ComposeTweetGoal = "authority" | "conversation" | "engagement" | "followers"; type MediaType = "none" | "photo" | "video"; type RadarSource = string; type RadarCategory = | "business" | "culture" | "dev" | "entertainment" | "general" | "politics" | "science" | "tech"; interface TweetMediaItem { mediaUrl: string; type: string; url: string; } interface StyleTweet { id: string; authorUsername: string; text: string; createdAt: string; media?: TweetMediaItem[]; } interface PerformanceTweet { id: string; text: string; likeCount: number; retweetCount: number; replyCount: number; quoteCount: number; viewCount: number; bookmarkCount: number; } ``` ## REST API vs MCP Field Naming REST examples show the default v1 response contract unless they send `xquik-api-contract: 2026-04-29`. API MCP v2.6.0 sends that contract automatically. MCP results use snake\_case, Unix timestamps, structured errors, `has_more`, and `next_cursor`. Pass `next_cursor` as `cursor` on tweet, profile, follower, reply, timeline, community, and list pages. Use `cursor` for draws, extractions, and events. Use `after` for Radar. Use `afterCursor` for drafts. A default `createdAt` field becomes `created`. ## Type reference ### Account Returned by `GET /api/v1/account`. The `creditInfo` field is omitted when no credit balance record exists yet. `creditInfo.balance` shows the current credit count available for metered API calls. ### API Keys Two shapes exist: `ApiKeyCreated` is returned only from `POST /api/v1/api-keys` and includes the `fullKey` field. This is the only time the full key is exposed. `ApiKey` is returned by `GET /api/v1/api-keys` and shows only the `prefix` (first 8 characters) for identification. ### Monitors Account monitor endpoints return `Monitor`. The `xUserId` is the X (Twitter) user ID resolved from the `username` at creation time. Keyword monitor endpoints return `KeywordMonitor` with the normalized X search `query`. Both monitor types include `eventTypes`, `isActive`, `createdAt`, and `nextBillingAt` for active monitor billing. ### Events `Event` represents one tracked account or keyword action. `monitorType` is `account` or `keyword`; `monitorId` points to the source monitor. Account events include `username`; keyword events include `query` and `keywordMonitorId`. `data` contains the documented Tweet event fields. `EventList` wraps paginated responses. Use `nextCursor` with the `cursor` query parameter for subsequent pages. ### Webhooks Similar to API keys, webhooks have two shapes. `WebhookCreated` includes the `secret` field used for HMAC signature verification. `Webhook` (from list) never exposes the secret. If you lose the secret, delete and recreate the webhook. ### Deliveries Each `Delivery` represents one attempt to send an event to a webhook endpoint. Status progresses from `pending` to `delivered` (success) or through `failed` to `exhausted` (all retries failed). `lastStatusCode` and `lastError` help diagnose delivery failures. ### Webhook payload The `WebhookPayload` type describes the JSON body your endpoint receives on each delivery. Normal monitor events include `schemaVersion`, `deliveryId`, `streamEventId`, `occurredAt`, `eventType`, and `data`. Account monitor events include `username`; keyword monitor events include `query`. Verify authenticity with the `X-Xquik-Signature` header and your webhook secret. See [Webhook Verification](/webhooks/verification) for implementation details. `webhook.test` payloads include `timestamp` and omit monitor-only fields because they are not tied to a stored event. In normal event payloads, `timestamp` is omitted. ### Draws `Draw` is the full draw object returned by `GET /api/v1/draws/{id}` with tweet metadata and engagement counts. `DrawListItem` is the compact shape returned in list responses. `DrawWinner` contains the position, username, tweet ID, and backup flag for each winner. Filter fields on `CreateDrawRequest` control which entries qualify for the draw. ### Extractions `ExtractionJob` represents a completed or failed extraction job. The target fields (`targetTweetId`, `targetUsername`, etc.) vary by `toolType`. `ExtractionResult` contains the core user and tweet data returned by the API. Results are paginated in the get endpoint with up to 1,000 results per page. Exports (CSV, XLSX, Markdown) include additional enrichment columns not present in the API response. See [Export Extraction](/api-reference/extractions/export) for the full column list. `ExtractionEstimate` previews the cost before running a job. ### X API Tweet, profile, and relationship lookup types. `Tweet` and `TweetAuthor` come from tweet lookup. `TweetSearchResult` includes an inline `author` object. `UserProfile` contains the full profile. `FollowerCheck` returns the bidirectional follow relationship between 2 users. `Tweet` and `TweetSearchResult` include an optional `media` array when media exists. Media types are `photo`, `video`, or `animated_gif`. ### Trends `Trend` represents a single trending topic on X. The `description`, `rank`, and `query` fields are omitted when unavailable. `TrendList` wraps the `GET /api/v1/trends` response. `total` is the number of trends returned, `woeid` is the region ID. ### Radar `RadarItem` represents a single trending topic or news item from Xquik's own infrastructure. The `source` field identifies the Radar stream. Reddit metadata can include post text, links, and media. It can also include public scores, estimated vote counts, and comment counts. It never includes comment bodies. Startup growth metadata can include reported growth and revenue. Company details and a founder `xHandle` appear when available. `RadarList` wraps paginated responses from `GET /api/v1/radar`. Use `nextCursor` with the `cursor` query parameter for subsequent pages. ### Styles `StyleProfile` is returned by the analyze style (`POST /api/v1/styles`) and get style (`GET /api/v1/styles/{id}`) endpoints. It includes the cached tweets array with text, media, and timestamps. `StyleListItem` is the compact shape returned by `GET /api/v1/styles` (no tweets). `StyleComparison` wraps two full profiles for side-by-side comparison. `PerformanceAnalysis` adds engagement metrics (likes, retweets, replies, quotes, views, bookmarks) to each cached tweet. `isOwnAccount` is `true` when the analyzed username matches the authenticated user's linked X identity. ### Drafts `Draft` represents a saved tweet draft. List, create, and get responses include `id`, `text`, `createdAt`, and `updatedAt`. Optional `topic` and `goal` fields are omitted (not null) when not set. `DraftList` wraps paginated responses. Use `nextCursor` with the `afterCursor` query parameter to fetch subsequent pages. Maximum 50 drafts per page. ### X Accounts `XAccount` represents a linked X account. `displayName` is the user's display name on X. `isActive` indicates whether the account is currently connected and usable for API operations. ### Compose `ComposeRequest` drives a 3-step writing flow. `compose` returns editorial rules and follow-up questions. It also returns 7 `radarRecommendations`, published signal names, and a topic `intentUrl`. Each recommendation identifies its endpoint, source, use case, and drafting guidance. Production signal weights are `null` because X does not publish them. `refine` returns writing guidance and 3 example patterns. `score` runs 9 deterministic text checks. These checks do not predict reach. `styleUsername` selects a cached style. Compose responses may include `savedStyles`, `styleTweets`, or `styleNote`. ### X Write `XWriteResponse` is returned by write operations (post, retweet, like, reply). `success` indicates whether the operation completed. The optional `data` field contains platform-specific response data when available. ### Support tickets `SupportTicket` is the compact shape returned in list responses. `SupportTicketDetail` includes the full `messages` array. Each `SupportMessage` has a `sender` field indicating whether the message is from the user or support team. Status progresses from `open` through `in_progress` to `resolved` or `closed`. ### Request bodies All request body types use optional fields for update operations (PATCH) and required fields for creation (POST). The API validates request bodies and returns `400 invalid_input` for missing or malformed fields. # Zapier Twitter Automation with Webhooks & X API Source: https://docs.xquik.com/guides/zapier Build Zapier Twitter automation for tweet search, follower exports, monitors, signed webhooks, extraction jobs, and approved X actions with exact code.
For the complete documentation index, see llms.txt.
Build Zapier Twitter automation through Xquik without an X Developer app. Search tweets, export followers, route webhooks, and approve write actions. Build a private Zapier Platform CLI integration. Add routes after Zap history confirms repeated demand. ## Replace the Retired Zapier Twitter Integration Zapier retired its native Twitter integration in 2023. Xquik restores those workflows through an API-key connection. Choose an Xquik connection based on the workflow: Use one reusable API-key connection for a small number of actions. Keep the Xquik key in the app connection instead of a Zap field. Use the CLI for shared actions, polling triggers, and REST Hooks. Add samples, output fields, custom errors, and shared field mapping. Use simple unauthenticated payloads only. Zapier keeps credentials in Webhooks step fields. People with Zap access can read them. Search existing tweets with an action. Monitor new tweets, replies, quotes, or reposts with polling or signed webhook events. This guide builds the private Platform integration. ## Prerequisites * [Xquik API key](/x-api-quickstart) * Zapier account with Platform CLI access * A Node.js release supported by the current Zapier Platform CLI * HTTPS callback URLs for REST Hook testing Install and sign in to the Zapier CLI: ```bash theme={null} npm install -g zapier-platform-cli zapier-platform --version zapier login zapier init xquik-zapier --template minimal cd xquik-zapier npm install ``` ## Integration Shape API key field named `apiKey`, injected as `x-api-key`. `https://xquik.com/api/v1` JSON requests, structured Xquik errors, and `Retry-After` handling. Search Tweets, Get User, Read Followers, Create Extraction, and Create Monitor. Add Create Webhook, Create Tweet, and Create Reply. New Matching Tweet polling and Monitor Event instant trigger. Add Extraction Completed and Webhook Delivery Failure polling. ## API Key Auth Add a custom auth field and inject it into every Xquik request: ```javascript theme={null} const BASE_URL = "https://xquik.com/api/v1"; function addApiKeyHeader(request, z, bundle) { request.headers = request.headers || {}; request.headers["x-api-key"] = bundle.authData.apiKey; return request; } async function testAuth(z) { const response = await z.request({ method: "GET", url: `${BASE_URL}/account`, }); return response.data; } module.exports = { authentication: { type: "custom", fields: [{ key: "apiKey", label: "Xquik API Key", required: true }], test: testAuth, }, beforeRequest: [addApiKeyHeader], }; ``` ## Shared Error Handling Normalize Xquik responses in one helper so every action reports the same remediation: ```javascript theme={null} function throwForXquikError(z, response) { if (response.status < 400) { return; } const detail = response.data?.message || response.data?.error; if (response.status === 400) { throw new z.errors.Error( detail || "Invalid request. Fix the input fields.", "XquikInvalidRequest", 400, ); } if (response.status === 401) { throw new z.errors.RefreshAuthError( "Authentication failed. Check the Xquik API key.", ); } if (response.status === 402) { throw new z.errors.Error( "Subscription or credits required. Update billing in Xquik.", "XquikBillingRequired", 402, ); } if (response.status === 404) { throw new z.errors.Error( detail || "Resource not found. Check the supplied ID.", "XquikResourceMissing", 404, ); } if (response.status === 424) { throw new z.errors.Error( detail || "X dependency failed. Retry only when the workflow is safe.", "XquikDependencyFailure", 424, ); } if (response.status === 429) { const retryAfter = Number.parseInt( response.getHeader?.("retry-after") || response.headers?.["retry-after"] || "", 10, ); if (Number.isInteger(retryAfter) && retryAfter > 0) { throw new z.errors.ThrottledError( "Rate limited. Zapier scheduled a retry.", retryAfter, ); } throw new z.errors.Error( "Rate limited. Retry after the cooldown period.", "XquikRateLimit", 429, ); } if (response.status === 502) { throw new z.errors.Error( detail || "X retrieval failed. Retry with a bounded attempt count.", "XquikRetrievalFailure", 502, ); } throw new z.errors.Error( detail || "Xquik request failed. Inspect the safe status.", "XquikRequestError", response.status, ); } async function xquikRequest(z, options) { const response = await z.request({ ...options, skipThrowForStatus: true, throwForThrottlingEarly: false, }); throwForXquikError(z, response); return response; } ``` Call `xquikRequest(z, options)` before reading response data. Use each route's response widget as the canonical status list. ## Starter Actions Call `GET /x/tweets/search` with `q`. Use `cursor` for page loops. Keep `limit` on bounded resumes. Call `GET /x/tweets/{id}` with a tweet ID. Call `GET /x/users/{id}` with a username or numeric user ID. Call `GET /x/users/{id}/followers`. Preserve `next_cursor` between pages. Call `GET /x/users/{id}/following`. Keep following rows separate. Call `GET /x/tweets/{id}/replies`. Preserve parent and reply tweet IDs. Call `GET /x/trends` with optional `woeid` and `count`. Call `POST /x/tweets` with account, text, and optional public media URLs. Call `POST /x/tweets` with account, text, and `reply_to_tweet_id`. Call `POST /extractions` with `toolType`, query fields, and result limit. Call `POST /monitors` with username and event types. Call `POST /webhooks` with callback URL and event types. ## Result Handoff Use Zapier samples and `outputFields` so later Zap steps map stable values. Use snake\_case storage keys when direct API responses use camelCase. Return compact objects from actions and arrays from triggers. Return `id`, `tweet_id`, `text`, `author_username`, `created_at`, and `url`. Keep `has_next_page` and `next_cursor` when a Zap loops pages. Return source `id` as `user_id`. Keep `username`, `name`, `followers`, `verified`, and `profile_picture`. Store the input, `has_next_page`, and `next_cursor` separately. Store `user_id`, `username`, `name`, `followers`, `following`, `verified`, and `profile_picture`. Keep the source account, page cursor, and relationship direction. Return each trend `name`, `rank`, `query`, and `description`. Keep response `count`, `woeid`, and the selected region with the Zap run. Send a unique `Idempotency-Key`. Store `id`, `status`, `billing`, `result`, and `statusUrl`. Poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. For tweets or replies, pass public URLs in `media`. Do not send `media_ids`. For DMs, upload first. Pass 1 `media_id` in `media_ids`. Store `message_id` and leave `reply_to_message_id` unset. Return monitor `id`, `username`, `xUserId`, `eventTypes`, `isActive`, and `nextBillingAt`. Return webhook `id`, `url`, `eventTypes`, and one-time `secret`. Map production `deliveryId` to `delivery_id` for receiver retry deduplication. Map `streamEventId` to `stream_event_id` for event deduplication across webhook changes. Return `id`, `delivery_id`, and `stream_event_id`. Use delivery IDs for endpoint retries. Use event IDs across webhook changes. Call `GET /events` with `cursor` when a Zap needs replay. Map event and monitor IDs first. Then map `occurredAt`, `hasMore`, and `nextCursor` to snake\_case fields. Zapier controls responses from its generated hook URL. Use a relay when you need custom acceptance or replay rules. Keep signing values, raw bodies, raw signatures outside Zap history. Keep full headers outside business apps too. Return completed job `id`, `toolType`, and `status`. Store normalized IDs, status, `has_more`, and `next_cursor`. Then fetch detail rows. Map friendly region names to WOEID values. Submit the selected value to Xquik. Example search action: ```javascript theme={null} async function performSearchTweets(z, bundle) { const response = await xquikRequest(z, { method: "GET", url: `${BASE_URL}/x/tweets/search`, params: { q: bundle.inputData.q, queryType: bundle.inputData.queryType || "Latest", limit: bundle.inputData.limit || 25, cursor: bundle.inputData.cursor || undefined, }, }); const page = response.data; return (page.tweets || []).map((tweet) => ({ id: tweet.id, tweet_id: tweet.id, text: tweet.text, author_username: tweet.author?.username || null, created_at: tweet.createdAt || null, url: tweet.url || null, has_next_page: Boolean(page.has_next_page), next_cursor: page.next_cursor || null, })); } module.exports = { key: "search_tweets", noun: "Tweet", display: { label: "Search Tweets", description: "Find recent tweets that match a search query.", }, operation: { inputFields: [ { key: "q", label: "Query", required: true, type: "string" }, { key: "queryType", label: "Order", required: false, choices: ["Latest", "Top"], }, { key: "limit", label: "Limit", required: false, type: "integer" }, { key: "cursor", label: "Cursor", required: false, type: "string" }, ], perform: performSearchTweets, sample: { id: "1840000000000000000", tweet_id: "1840000000000000000", text: "Example tweet text", author_username: "example_user", created_at: "2026-08-02T12:00:00Z", url: "https://x.com/example_user/status/1840000000000000000", has_next_page: true, next_cursor: "DAACCgACExample", }, outputFields: [ { key: "id", label: "Tweet ID" }, { key: "tweet_id", label: "Tweet ID for Storage" }, { key: "text", label: "Text" }, { key: "author_username", label: "Author Username" }, { key: "created_at", label: "Created At" }, { key: "url", label: "URL" }, { key: "has_next_page", label: "Has Next Page", type: "boolean" }, { key: "next_cursor", label: "Next Cursor" }, ], }, }; ``` ## Trigger 1: New Matching Tweet Poll searches for query alerts. Return stable tweet records with unique IDs. ```javascript theme={null} async function performNewMatchingTweet(z, bundle) { return performSearchTweets(z, { ...bundle, inputData: { ...bundle.inputData, cursor: undefined, limit: 100, queryType: "Latest", }, }); } ``` Return newest tweets first. Never reuse saved cursors across polling runs. ## Trigger 2: Monitor Event REST Hook Use REST Hooks for near-real-time tweets, replies, quotes, and retweets. ### Choose a Secure Webhook Path Use a verification relay. Keep the one-time signing secret outside Zapier logs. Let the relay create the Xquik webhook. Store its secret only in the relay. Verify HMAC, timestamp, nonce, delivery ID, and event ID there. Forward a normalized event to `bundle.targetUrl` after verification. Use direct Zapier subscription only for isolated testing. The returned secret enters `bundle.subscribeData` and may enter Zapier HTTP logs. Never use this path when that exposure violates your secret policy. ### Direct Prototype Subscribe ```javascript theme={null} async function subscribeHook(z, bundle) { const response = await xquikRequest(z, { method: "POST", url: `${BASE_URL}/webhooks`, body: { url: bundle.targetUrl, eventTypes: bundle.inputData.eventTypes, }, }); return { id: response.data.id, secret: response.data.secret, eventTypes: response.data.eventTypes, }; } ``` Zapier stores this result in `bundle.subscribeData`. Never expose the `secret` through trigger output. Use this path only for isolated testing. ### Unsubscribe ```javascript theme={null} async function unsubscribeHook(z, bundle) { const response = await xquikRequest(z, { method: "DELETE", url: `${BASE_URL}/webhooks/${bundle.subscribeData.id}`, }); return response.data; } ``` ### Direct Prototype Perform ```javascript theme={null} const { createHmac, timingSafeEqual } = require("node:crypto"); const FIVE_MINUTES_MS = 5 * 60 * 1000; function readRawHeader(headers, requestedName) { const wanted = requestedName.toLowerCase(); for (const [name, value] of Object.entries(headers || {})) { const normalized = name.toLowerCase().replace(/^http-/, ""); if (normalized === wanted) { return String(value); } } return ""; } function verifyXquikWebhook(z, bundle) { const rawBody = bundle.rawRequest?.content; const headers = bundle.rawRequest?.headers; const secret = bundle.subscribeData?.secret; const timestamp = readRawHeader(headers, "x-xquik-timestamp"); const nonce = readRawHeader(headers, "x-xquik-nonce"); const signature = readRawHeader(headers, "x-xquik-signature"); if (!rawBody || !secret || !timestamp || !nonce || !signature) { throw new z.errors.Error( "Webhook rejected. Signing fields are missing.", "XquikWebhookVerificationError", 400, ); } const timestampMs = Number(timestamp); if ( !Number.isFinite(timestampMs) || Math.abs(Date.now() - timestampMs) > FIVE_MINUTES_MS ) { throw new z.errors.Error( "Webhook rejected. Timestamp is stale or invalid.", "XquikWebhookVerificationError", 400, ); } const signingString = `${timestamp}.${nonce}.${rawBody}`; const expected = "sha256=" + createHmac("sha256", secret).update(signingString).digest("hex"); const expectedBuffer = Buffer.from(expected); const actualBuffer = Buffer.from(signature); if ( expectedBuffer.length !== actualBuffer.length || !timingSafeEqual(expectedBuffer, actualBuffer) ) { throw new z.errors.Error( "Webhook rejected. Signature does not match.", "XquikWebhookVerificationError", 400, ); } return JSON.parse(rawBody); } function performMonitorEvent(z, bundle) { const payload = verifyXquikWebhook(z, bundle); return [ { id: payload.streamEventId || payload.deliveryId || String(payload.timestamp), delivery_id: payload.deliveryId || null, stream_event_id: payload.streamEventId || null, event_type: payload.eventType, occurred_at: payload.occurredAt || payload.timestamp, username: payload.username || null, tweet_id: payload.data?.id || null, text: payload.data?.text || null, author_username: payload.data?.author?.userName || payload.username || null, }, ]; } ``` Verify `bundle.rawRequest.content` before using `bundle.cleanedRequest`. Zapier prefixes most inbound raw headers with `Http-`; normalize that prefix. Zapier does not provide a durable 5-minute nonce store. Place a durable verification relay before Zapier in production. Verify HMAC, timestamps, and nonces there. Deduplicate `deliveryId` and `streamEventId` before forwarding. Zapier owns responses from `bundle.targetUrl`. ```javascript theme={null} async function performList(z) { const response = await xquikRequest(z, { method: "GET", url: `${BASE_URL}/events`, params: { limit: 1 }, }); return (response.data.events || []).slice(0, 1).map((event) => ({ id: event.id, delivery_id: null, stream_event_id: event.id, event_type: event.type, occurred_at: event.occurredAt, username: event.username || null, tweet_id: event.data?.id || null, text: event.data?.text || null, author_username: event.data?.author?.userName || event.username || null, })); } ``` Testing a REST Hook calls `performList`, not the live `perform` handler. ## Trigger 3: Extraction Completed ```javascript theme={null} async function performCompletedExtractions(z) { const response = await xquikRequest(z, { method: "GET", url: `${BASE_URL}/extractions`, params: { status: "completed", limit: 25 }, }); return response.data.extractions || []; } ``` ## Trigger 4: Webhook Delivery Failure ```javascript theme={null} async function performWebhookFailures(z, bundle) { const response = await xquikRequest(z, { method: "GET", url: `${BASE_URL}/webhooks/${bundle.inputData.webhookId}/deliveries`, }); return (response.data.deliveries || []).filter( (delivery) => delivery.status === "failed", ); } ``` ## Build Useful Zapier Twitter Automation Search tweets with one saved query. Append IDs, text, authors, times, and URLs to Google Sheets. Resume with that query's `next_cursor`. Verify each monitor event. Send normalized tweets, accounts, event types, and URLs to Slack in real time. Send matched tweets or replies to Discord. Include the author, timestamp, tweet URL, and search query. Read blog posts from an RSS feed. Create approved scheduled tweets with the article title, URL, and campaign tags. Review the Twitter account, text, reply target, and media. Then run the approved write action with an idempotency key. Use an automated workflow for bounded follower pages. Choose an extraction job for durable CSV, JSON, or XLSX exports. Zapier Twitter Google Sheets workflows store searchable tweet rows. Zapier Twitter to Slack sends verified alerts in real time. Zapier Twitter Discord sends matched tweet notifications. Zapier RSS to Twitter converts blog posts from an RSS feed. Review scheduled tweets and the Twitter account before scheduling posts. Approve every Zapier post to Twitter step. Keep each automated workflow focused. Compare other Twitter automation tools before choosing. ## Test Coverage Add focused Zapier tests before sharing the private app: Every request includes `x-api-key` from `bundle.authData.apiKey`. `401` returns "Authentication failed. Check the Xquik API key." `400` returns the safe Xquik detail or an input-field remediation. `402` tells the user to update subscription or credits. `404` identifies a bad tweet, user, monitor, webhook, or extraction ID. `424` stops unsafe automatic retries and preserves a safe explanation. `429` creates `ThrottledError` with `Retry-After` when present. `502` uses a bounded retry policy outside unsafe write actions. The relay stores `bundle.targetUrl`, selected event types, and the secret. `DELETE /webhooks/{id}` uses `bundle.subscribeData.id`. `performList` and live `perform` return identical snake\_case fields. Search returns stable tweet IDs, authors, timestamps, URLs, and cursors. Changing one raw body byte makes HMAC verification fail. A timestamp outside 5 minutes is rejected before JSON parsing. Production Zapier output and logs never receive the Xquik signing secret. The relay rejects reused nonces and deduplicates delivery and event IDs. Relay tests cover custom `2xx` rules. Zapier owns its hook URL response. Run the local Zapier suite with: ```bash theme={null} zapier-platform test zapier-platform validate ``` ## Zapier Twitter Automation Questions ### How Does a Zapier Twitter API Connection Work? Yes. A private Platform app calls Xquik with one API key. ### Why Does the Native Zapier Twitter Integration Not Work? Zapier retired it in 2023. Use a private Platform integration. ### What Does a Twitter Zapier Workflow Replace? It replaces manual copying across tweet workflows. ### Can Zapier Search Tweets by Keyword? Yes. Search with `q`; store tweet IDs and cursors. ### Can Zapier Export Twitter Followers? Yes. Page small lists; export larger ones. ### Should a Zap Poll or Use a Twitter Webhook? Poll delayed searches. Use signed hooks for live monitors. ### How Does Zapier Verify an Xquik Webhook? Use a production relay. Verify HMAC over the raw body there. Check the timestamp and nonce before parsing JSON. Reject reused nonces. Deduplicate delivery and event IDs before forwarding events to Zapier. ### Can Zapier Post Tweets and Replies? Yes. Use Zapier to automate tweets only after explicit approval. ### Can Zapier Schedule Posts From an RSS Feed? Convert RSS items into scheduled tweets. Approve them before publishing. ## Zapier and Xquik Sources * [Zapier Platform CLI quickstart](https://docs.zapier.com/integrations/quickstart/build-integration) * [Zapier Twitter integration removal](https://help.zapier.com/hc/en-us/articles/43554846358541-App-update-Twitter-integration-removal) * [Zapier API request options](https://help.zapier.com/hc/en-us/articles/44391646192397-Ways-to-make-API-requests-in-Zapier) * [Zapier REST Hook trigger](https://docs.zapier.com/integrations/build/cli-hook-trigger) * [Zapier bundle reference](https://docs.zapier.com/integrations/build/bundle) * [Zapier HTTP request logging](https://docs.zapier.com/integrations/build-cli/making-http-requests) * [Zapier integration monitoring](https://docs.zapier.com/integrations/build/test-monitoring) * [Zapier throttling guidance](https://docs.zapier.com/integrations/build/troubleshoot-throttles) * [Xquik webhook verification](/webhooks/verification) * [Xquik error handling](/guides/error-handling) * [Xquik follower export](/guides/follower-export-crm) ## Next Steps Read [API Reference](/api-reference/overview) for statuses, [Webhooks](/webhooks/overview) for verification, and [Extraction Workflow](/guides/extraction-workflow) for jobs. # AI Agent MCP Handoff for Tweet Search & Exports Source: https://docs.xquik.com/mcp/agent-handoff Route AI agents between tweet search, follower exports, account actions, Docs MCP, API MCP, REST, SDKs, webhooks, and event replay. See tool examples.
For the complete documentation index, see llms.txt.
Use this page when an AI agent needs to choose the right Xquik surface. It can search tweets, inspect profiles, export followers, or recover missed webhooks. Keep live calls narrow. Aggregate high-volume pages inside the sandbox. Persist cursor state. Move durable jobs to REST, SDKs, or webhooks. ## Pick the agent surface Use `https://docs.xquik.com/mcp` when the agent needs public docs, API reference pages, examples, troubleshooting, or type definitions. It is read-only and requires no auth. Use API MCP v2.6.0 at `https://xquik.com/mcp` for live calls. Full account keys and OAuth tokens expose 120 catalog routes. Guest `paid_reads` keys expose exactly 33 eligible GET routes. Current SDKs negotiate MCP `2026-07-28` through `server/discover`. Use REST or generated SDKs when a service owns retries, cursor storage, file downloads, queues, or batch jobs outside the chat session. Use monitor webhooks for fresh events and `GET /api/v1/events` when a receiver, queue, warehouse, or agent run needs replay. ## Use the default agent route 1. Search public docs or `llms.txt` before requesting current API data. 2. Use `explore` before `xquik.request(...)` to confirm the endpoint path, required parameters, costs, and response shape. 3. Call `xquik.request(path, { method?, body?, query? })` with the smallest useful page size. Hosted MCP injects authentication and required idempotency headers. 4. Continue while `has_more` is true and `next_cursor` advances. 5. Return normalized rows, IDs, counts, samples, and cursors instead of full raw pages. 6. Hand long-running or replayable work to REST, SDKs, webhooks, or exports. Credential lifecycle operations and direct saved-payment mutations are not in the MCP catalog. Guest wallet creation, status, and top-up also remain direct REST only. A `402` creates no checkout. Report its payment choices, ask the user to choose an amount and option, then wait for explicit confirmation. A full account session may execute only an advertised account checkout action in its catalog. Never execute guest wallet routes through MCP. ```javascript theme={null} async () => { const matches = spec.endpoints.filter((endpoint) => endpoint.path.includes('/x/tweets/search') || endpoint.summary.toLowerCase().includes('followers') ); return matches.map(({ method, path, summary, parameters, responseShape }) => ({ method, path, summary, parameters, responseShape })); } ``` ## Route guest paid reads An active guest key gives `explore` and `xquik` a 33-operation read-only catalog. Every route appears in the [guest paid-read inventory](/guides/guest-wallets#eligible-paid-read-routes). Batch `GET /api/v1/x/tweets` accepts up to 100 tweet IDs. The sandbox cannot execute writes, account actions, automations, billing, credential management, or noneligible reads. OAuth and full account behavior remain unchanged. If the guest key needs creation, activation status, or more credits, leave MCP and follow the [accountless guest wallet flow](/guides/guest-wallets) through direct REST: * [`POST /api/v1/guest-wallets`](/api-reference/guest-wallets/create) * [`GET /api/v1/guest-wallets/status`](/api-reference/guest-wallets/status) * [`POST /api/v1/guest-wallets/topups`](/api-reference/guest-wallets/topup) Creation and top-up require explicit user confirmation. Status polling does not create payment. Never place the guest key or its creation `Idempotency-Key` in tool code, chat output, or shared agent state. ## Return stored rows MCP returns normalized snake\_case fields, structured errors, and date-time fields as Unix seconds. Keep agents on `has_more` and `next_cursor` even when REST or SDK pages show camelCase response fields. Read a REST `createdAt` field as `created` in MCP results. ```javascript theme={null} async () => { const query = 'from:username MCP'; const page = await xquik.request('/api/v1/x/tweets/search', { query: { q: query, limit: '50' } }); return { source: 'xquik_mcp', job: 'tweet_search', query, rows: page.tweets.map((tweet) => ({ tweet_id: tweet.id, text: tweet.text ?? null, author_id: tweet.author?.id ?? null, author_username: tweet.author?.username ?? null, created_at: tweet['created'] ?? null, url: tweet.url ?? null })), has_more: page.has_more, next_cursor: page.next_cursor }; } ``` ## Follow cursor rules | Source | Next request | | ------------------------------------------------------------------------ | ------------------------------------------------------------ | | MCP tweet, profile, follower, reply, timeline, community, and list pages | Pass `next_cursor` back as `cursor` when `has_more` is true. | | MCP `/api/v1/draws`, `/api/v1/extractions`, and `/api/v1/events` | Pass `next_cursor` back as `cursor`. | | MCP `/api/v1/radar` | Pass `next_cursor` back as `after`. | | MCP `/api/v1/drafts` | Pass `next_cursor` back as `afterCursor`. | | REST and generated SDKs | Follow the response fields documented on that endpoint page. | Full account sessions can check `GET /api/v1/credits` before large reads. Guest sessions receive balance and top-up context in `402`, while the direct REST guest status route reports the current balance. Low balances can return smaller pages, and zero affordable rows can return `402 insufficient_credits`. For high-volume MCP reads: * De-duplicate tweet or user rows by stable `id` * Continue through empty pages when `has_more` is true * Stop at the requested total, page cap, or `has_more: false` * Stop with `cursor_stalled` when `next_cursor` is missing or repeats * Return counts, aggregates, or a bounded sample within the 24,000-character tool limit ## Persist handoff state Store the values a later agent, service, or workflow needs to resume without reading chat history. ```json theme={null} { "agent_job_id": "mcp-research-q2", "surface": "api_mcp", "endpoint": "GET /api/v1/x/tweets/search", "query": "from:username MCP", "cursor_param": "cursor", "next_cursor": "DAACCgACGRElMJcAAA", "has_more": true, "webhook_id": "15", "delivery_id": "502", "stream_event_id": "9002", "event_replay_route": "GET /api/v1/events?cursor=9002", "export_route": "GET /api/v1/extractions/77777/export?format=json", "saved_fields": ["tweet_id", "author_username", "text", "url"] } ``` Keep API keys, webhook secrets, raw request bodies, raw signatures, and full headers out of chat transcripts, shared agent memory, spreadsheets, CRM rows, and queue payloads. ## Know when to leave MCP Use extraction export endpoints for CSV, JSON, XLSX, Markdown, or PDF files. Use signed webhooks when downstream systems need fresh monitor events. Use stored events and delivery rows when receivers miss work. Use SDKs when a backend owns retries, storage, and batch orchestration. ## Continue with focused references Review `explore`, `xquik`, sandbox inputs, response contracts, and examples. Connect read-only documentation search beside the API MCP server. Hand monitor events, exports, and direct reads to workflow platforms. Verify signed receivers before accepting production events. # Docs MCP Server for Tweet, Follower & Webhook Docs Source: https://docs.xquik.com/mcp/docs-mcp Search Xquik tweet, follower, profile, monitor, webhook, extraction, SDK, MCP, and account-action documentation from AI tools. Includes OAuth and tool examples.
For the complete documentation index, see llms.txt.
Xquik documentation is available as an MCP server at `https://docs.xquik.com/mcp`. AI tools can search the full docs site and retrieve indexed public pages - API reference, guides, examples, and type definitions - during conversations. This differs from the [Xquik API MCP server](/mcp/overview) at `xquik.com/mcp`. That server searches tweets, looks up profiles, exports followers, runs draws, and manages monitors. The docs MCP server is read-only and requires no authentication. Codex and Goose can connect to this authentication-free Docs MCP server even when API MCP OAuth stops with `Authorization server response missing required issuer: expected https://xquik.com`. Retrieve [Codex and Goose OAuth issuer validation](/guides/troubleshooting#codex-oauth-issuer-validation-error), then configure the API-key fallback for the authenticated API MCP server. Codex users can track the [upstream issuer issue](https://github.com/openai/codex/issues/31573). Search docs and read indexed public pages at `https://docs.xquik.com/mcp`. No auth required. Free. Search tweets, inspect profiles, export followers, and manage monitors at `https://xquik.com/mcp`. Use an API key or OAuth 2.1. Prefer OAuth for clients that support browser authorization. Full credentials search 120 catalog routes. Guest `paid_reads` keys search 33 eligible GET routes. API MCP v2.6.0 supports MCP `2026-07-28` over Streamable HTTP. ## Agent route checklist Use Docs MCP at `https://docs.xquik.com/mcp` for public docs, API parameters, examples, error codes, SDK guidance, and no-auth page retrieval. Use API MCP at `https://xquik.com/mcp` for live X reads, writes, monitors, webhooks, draws, or extraction jobs. Authenticate with an API key or OAuth 2.1. Prefer OAuth when the client supports browser authorization. Use REST or generated SDKs when a backend must own retries, cursor storage, file downloads, queues, or batch orchestration. Store the endpoint path, request parameters, returned IDs, `has_more`, `next_cursor`, export route, or webhook replay route before ending the agent run. ## Quick connect Use the contextual menu at the top of any docs page: * **Copy MCP Server URL** - copies `https://docs.xquik.com/mcp` to your clipboard * **Copy MCP Install Command** - copies the `npx add-mcp` install command * **Connect to Cursor** - installs the docs MCP server in Cursor * **Connect to VS Code** - installs the docs MCP server in VS Code ## Setup ### Web clients 1. Open [Claude Connectors](https://claude.ai/settings/connectors) or **Customize > Connectors**. 2. Select **Add custom connector**. 3. Enter: * Name: `Xquik Docs` * URL: `https://docs.xquik.com/mcp` 4. Select **Add**. 5. In a chat, select **+ > Connectors** and enable **Xquik Docs**. 1. In ChatGPT on the web, open **Settings > Security and login**. Enable **Developer mode**. 2. Open [**Settings > Plugins**](https://chatgpt.com/plugins). Select **+**. 3. Enter `https://docs.xquik.com/mcp`, then select **Create**. 4. Start a new chat. Select **+ > More**, then select Xquik Docs. The Docs MCP server uses only read and fetch operations, so it fits ChatGPT Pro's current custom-app permission limit. Link it on the web first. The linked app then appears on mobile. Follow [OpenAI's current setup guide](https://developers.openai.com/apps-sdk/deploy/connect-chatgpt) when ChatGPT changes its labels. ### Coding agents ```bash theme={null} claude mcp add --transport http xquik-docs https://docs.xquik.com/mcp ``` Verify with: ```bash theme={null} claude mcp list ``` ```bash theme={null} codex mcp add xquik-docs --url https://docs.xquik.com/mcp codex mcp list ``` ```bash theme={null} copilot mcp add --transport http xquik-docs https://docs.xquik.com/mcp ``` If your installed build does not expose these flags, start Copilot CLI and run `/mcp add`. Choose **HTTP**, enter the server name and URL, keep `*` for tools, then press **Ctrl+S**. Verify it with `/mcp show xquik-docs`. ```bash theme={null} gemini mcp add --transport http xquik-docs https://docs.xquik.com/mcp ``` Or add this entry under `mcpServers` in `~/.gemini/settings.json` or `.gemini/settings.json`: ```json theme={null} { "xquik-docs": { "type": "http", "url": "https://docs.xquik.com/mcp" } } ``` Older Gemini CLI builds also accept the legacy `httpUrl` field. ### Additional coding agents ```bash theme={null} qwen mcp add --transport http xquik-docs https://docs.xquik.com/mcp ``` Manual Qwen Code configuration still uses `httpUrl: "https://docs.xquik.com/mcp"` under `mcpServers`. Start a session with the Docs MCP extension: ```bash theme={null} goose session --with-streamable-http-extension https://docs.xquik.com/mcp ``` Docs MCP needs no OAuth or API-key header, so affected Goose issuer callback defect does not affect this connection. Pi has no native MCP client. Install and audit a community MCP adapter before adding `https://docs.xquik.com/mcp`. Xquik does not claim native Pi support. ### Editor clients Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project): ```json theme={null} { "mcpServers": { "xquik-docs": { "url": "https://docs.xquik.com/mcp" } } } ``` Add to `.vscode/mcp.json` (project) or use **MCP: Open User Configuration** (global): ```json theme={null} { "servers": { "xquik-docs": { "type": "http", "url": "https://docs.xquik.com/mcp" } } } ``` Add to `~/.codeium/windsurf/mcp_config.json`: ```json theme={null} { "mcpServers": { "xquik-docs": { "serverUrl": "https://docs.xquik.com/mcp" } } } ``` Add to `opencode.json`: ```json theme={null} { "mcp": { "xquik-docs": { "type": "remote", "url": "https://docs.xquik.com/mcp" } } } ``` ### Additional editors Run `cline mcp`, add a Streamable HTTP server, and enter `https://docs.xquik.com/mcp`. In the IDE, use the **MCP Remote Servers** UI. Cline stores global MCP settings at `~/.cline/data/settings/cline_mcp_settings.json`; project settings use `.cline/mcp.json`. Roo Code is archived. Its final release can still connect to this authentication-free server. Add to project `.roo/mcp.json` or global `mcp_settings.json`: ```json theme={null} { "mcpServers": { "xquik-docs": { "type": "streamable-http", "url": "https://docs.xquik.com/mcp" } } } ``` ## Using both MCP servers For the best experience, connect both: 1. **Docs MCP** (`docs.xquik.com/mcp`) - the AI searches documentation to understand API parameters, error codes, and usage patterns. 2. **API MCP** (`xquik.com/mcp`) - the AI executes actions: searches tweets, runs extractions, sets up monitors. The AI decides which server to query based on context. A question about how draw filters work hits the docs server. A request to run a draw hits the API server. API MCP results use the normalized v1 contract: snake\_case fields, date-time fields as Unix seconds, structured errors, `has_more`, and `next_cursor`. Docs MCP returns indexed documentation text and never executes account actions. ```bash theme={null} claude mcp add --transport http xquik-docs https://docs.xquik.com/mcp claude mcp add --transport http xquik https://xquik.com/mcp ``` Run `/mcp` and authenticate `xquik`. The docs server needs no login. ```bash theme={null} codex mcp add xquik-docs --url https://docs.xquik.com/mcp codex mcp add xquik --url https://xquik.com/mcp codex mcp login xquik ``` Docs MCP needs no login. If API MCP reports `Authorization server response missing required issuer: expected https://xquik.com`, keep Docs MCP connected and follow [Codex and Goose OAuth issuer validation](/guides/troubleshooting#codex-oauth-issuer-validation-error). ```json theme={null} { "mcpServers": { "xquik-docs": { "url": "https://docs.xquik.com/mcp" }, "xquik": { "url": "https://xquik.com/mcp" } } } ``` ```json theme={null} { "servers": { "xquik-docs": { "type": "http", "url": "https://docs.xquik.com/mcp" }, "xquik": { "type": "http", "url": "https://xquik.com/mcp" } } } ``` ## What gets searched The docs MCP server indexes all public pages: * API reference (128 documented operations) * Guides (workflows, error handling, rate limits, billing, extraction workflow, trends, webhook testing) * Webhook documentation (overview, signature verification) * MCP server setup and tools reference * OAuth 2.1 documentation * Architecture, troubleshooting, and type definitions * `llms.txt` (complete API technical reference) # Connect AI Agents via MCP for MCP & X API Agents Source: https://docs.xquik.com/mcp/overview Connect AI agents to tweet search, profile lookup, follower exports, monitors, webhooks, and account actions with OAuth 2.1 and MCP. See tool examples.
For the complete documentation index, see llms.txt.
Xquik API MCP v2.6.0 exposes a scoped REST catalog through 2 [Model Context Protocol](https://modelcontextprotocol.io) tools. Full credentials see 120 catalog routes. Of these, 119 return JSON or text through MCP. Private support media downloads use REST. Guest `paid_reads` keys see exactly 33 GET routes. Public tweet, profile, follower, reply, timeline, community, and list reads need no connected X account. Every X write requires one. Private reads, including DMs and bookmarks, also require one. See [Connect X account](/api-reference/x-accounts/connect). This page covers the API MCP server at `https://xquik.com/mcp` for authenticated account actions and guest paid reads. For public documentation search, use the [Docs MCP server](/mcp/docs-mcp) at `https://docs.xquik.com/mcp`. Affected Codex and Goose releases discard the RFC 9207 `iss` value before token exchange. Xquik already returns the required issuer. Follow [Codex and Goose OAuth issuer validation](/guides/troubleshooting#codex-oauth-issuer-validation-error) to use an environment-backed API key until your client includes a fix. ## Connection Model Context Protocol over Streamable HTTP. Connect clients to `https://xquik.com/mcp`. Current API MCP server version: `2.6.0`. Prefer OAuth 2.1. API keys remain available for clients with secure header storage. Xquik compatibility discovery metadata is available at: ```text theme={null} https://xquik.com/.well-known/mcp.json ``` `GET` and `POST` requests to `/.well-known/mcp.json` return an Xquik compatibility discovery document based on the official MCP Registry `server.json` manifest. `GET /server.json` and `GET /.well-known/mcp/server-card.json` return the same compatibility document. Its standard `remotes` entry identifies the `streamable-http` endpoint. Extra top-level convenience fields preserve compatibility with older clients, but they are not MCP Registry or experimental MCP Server Card fields. OAuth-aware clients read `GET /.well-known/oauth-protected-resource/mcp` for protected-resource metadata for `https://xquik.com/mcp`. Compatibility clients can also read `GET /.well-known/oauth-protected-resource/.well-known/mcp.json`, which redirects to the canonical metadata URL. Registry-compatible clients receive a `streamable-http` remote for `https://xquik.com/mcp`. OAuth-capable clients discover authentication from the endpoint. Clients without OAuth may send an API key as `Authorization: Bearer {XQUIK_API_KEY}` or `x-api-key: {XQUIK_API_KEY}`. Create API keys at `https://dashboard.xquik.com/en/account?tab=api-keys`. The direct client examples below use OAuth. Use the API-key fallback only when the client documents secure request headers. Agent discovery metadata is also available at `https://xquik.com/.well-known/agent-index.json`. That index lists `com.xquik/mcp`, `https://xquik.com/mcp`, `https://xquik.com/.well-known/mcp.json`, the OAuth authorization metadata, the protected-resource metadata, and `https://xquik.com/auth.md`. The `auth.md` file explains Client ID Metadata Documents (CIMD), Dynamic Client Registration (DCR), PKCE, and the `mcp:tools` scope. DCR at `https://xquik.com/api/oauth/register` is the supported anonymous OAuth client registration path when a client cannot use CIMD. Agent Skills discovery is available at `https://xquik.com/.well-known/agent-skills/index.json`. It publishes a SHA-256 digest for Xquik's hosted `SKILL.md` so compatible agents can verify the downloaded instructions. ## MCP 2026-07-28 Xquik supports MCP `2026-07-28` at the same Streamable HTTP endpoint. Current clients start with `server/discover`. They do not call `initialize` or create a session for a modern connection. Use a current MCP SDK. It adds the request `_meta` envelope and required HTTP headers automatically. Modern requests must advertise both `application/json` and `text/event-stream`. `server/discover` and `tools/list` include private cache hints with a 5-minute TTL. Clients can reuse those results for the same authorization context. Never share privately cached catalogs across users or credentials. Xquik also accepts stateless 2025-era clients at the same endpoint. This keeps existing integrations working while current SDKs adopt `2026-07-28`. Modern Xquik connections are request-scoped. Ignore legacy session IDs and resume state. Let the client SDK negotiate the protocol. Unauthenticated requests to `https://xquik.com/mcp` return `401` with a `WWW-Authenticate: Bearer` challenge. The challenge includes `resource_metadata="https://xquik.com/.well-known/oauth-protected-resource/mcp"`, `scope="mcp:tools"`, and the OAuth realm. The JSON body is `{ "error": "Authentication required" }`. OAuth-capable clients use the challenge to discover the authorization metadata. API-key clients should send `x-api-key` on the first request. A supplied invalid bearer token adds `error="invalid_token"` and `error_description="Invalid access token"` to the challenge. ## Authentication The MCP server supports 2 authentication methods: * **OAuth 2.1** (recommended): Compatible clients discover Xquik, open the browser login and consent flow, then store and refresh Bearer tokens. Xquik supports CIMD and DCR. No manual client ID, client secret, or API key is required for normal client setup. * **API key** (`x-api-key` or `Authorization: Bearer xq_your_api_key_here`): This is an Xquik-specific fallback, not an OAuth token. Do not apply OAuth discovery or refresh rules. Use it only with secure header storage. Full account keys expose 120 catalog routes. Active guest keys expose 33 `paid_reads` GET routes. See [OAuth 2.1 authorization](/oauth/overview) for discovery URLs, token lifetimes, client registration, and implementation details. OAuth and full account API key behavior remain unchanged. A pending guest key cannot execute paid reads until verified payment activates it. ## How it works The MCP server uses a **code-execution sandbox model** with 2 tools: Search the authenticated catalog. Full credentials see 120 routes. Guest keys see 33 GET routes. No network calls. No credits. Execute authenticated API calls. Cost follows the endpoint. The AI agent writes async JavaScript arrow functions that run in a sandboxed environment. Authentication and required idempotency headers are injected automatically. The code-mode design keeps the endpoint catalog outside the client context. Both tools publish titles and Model Context Protocol safety annotations so clients can distinguish read-only discovery from authenticated execution. | Tool | Title | Safety annotations | | --------- | ------------------- | --------------------------------------------------------- | | `explore` | Explore Xquik API | Read-only, idempotent, closed-world, non-destructive | | `xquik` | Run Xquik API Calls | May mutate data, may access live services, not idempotent | For a guest `paid_reads` session, `xquik` is read-only, idempotent, and limited to live calls across the 33 eligible GET routes. ### `explore` tool Searches the 120-route full account catalog. The call uses no credits. MCP authentication remains required. The sandbox provides: With a guest `paid_reads` key, `spec.endpoints` contains only the 33 eligible GET read routes. ```typescript theme={null} interface EndpointInfo { method: string; path: string; summary: string; operationId: string; category: string; // account, composition, credits, extraction, media, monitoring, support, twitter, x-accounts, x-write free: boolean; injectedHeaders?: string[]; parameters?: Array<{ name: string; in: 'query' | 'path' | 'body'; required: boolean; type: string; description: string }>; responseShape?: string; } declare const spec: { endpoints: EndpointInfo[] }; ``` ### `xquik` tool Executes API calls. The sandbox provides: ```typescript theme={null} declare const xquik: { request(path: string, options?: { method?: string; // default: 'GET' body?: unknown; query?: Record; }): Promise; }; declare const spec: { endpoints: EndpointInfo[] }; ``` The agent writes code like `async () => xquik.request('/api/v1/radar')`. The server injects authentication and required idempotency headers. It reuses each generated key for bounded transient retries. After an unresolved write failure, verify state. Start a new attempt only when `safe_to_retry` is true. `xquik.request()` automatically uses the normalized v1 contract. Responses use snake\_case fields, date-time fields as Unix seconds, structured error objects, `has_more`, and `next_cursor`. A default REST `createdAt` field becomes `created`, not `created_at`, in MCP results. ### MCP operation boundary The REST contract documents 128 operations. Full credentials expose 120 MCP catalog routes. These 8 credential and session operations stay outside the catalog: * Create, list, or revoke account API keys * Charge a saved payment method through quick top-up * Open the session-based account top-up redirect route * Create, poll, or top up a guest wallet The catalog includes private support attachment downloads. MCP rejects their binary responses. Use the REST download endpoint instead. The other 119 routes return MCP-compatible JSON or text. Guest wallet credential routes remain direct REST only. MCP cannot execute `POST /api/v1/guest-wallets`, `POST /api/v1/guest-wallets/topups`, or `GET /api/v1/guest-wallets/status`. Follow the [accountless guest wallet guide](/guides/guest-wallets) for confirmation, checkout, polling, and top-up steps. A guest `paid_reads` MCP session exposes exactly the [33 eligible paid-read routes](/guides/guest-wallets#eligible-paid-read-routes). It cannot execute mutations or noneligible routes. Never start checkout, top-up, subscription, or billing actions because another call returned `402`. Report the choices, ask the user to select an amount and option, then wait for explicit confirmation. After confirmation, MCP may execute only an account checkout action present in the full catalog. Guest wallet actions remain direct REST. ## MCP vs REST API MCP follows REST authentication, authorization, billing, and response contracts for every exposed operation. Use MCP for agents and IDE integrations. Full credentials expose 120 catalog routes. Guest keys expose 33 GET reads. Use REST for binary support downloads. Best for backend services, automation scripts, guest wallet credential routes, and direct programmatic access. The REST contract documents all 128 operations and file download responses. **When to use MCP:** You're building an AI agent or working in an IDE. MCP lets the agent search tweets, inspect profiles, export followers, monitor accounts, and post through natural language. **When to use REST:** You're building a backend service, automation pipeline, or need fine-grained control over API calls, pagination, and file exports. Start with [Claude.ai](https://claude.ai) for OAuth login or [Claude Code](#setup) for terminal setup. ## Client compatibility Choose the authentication path that your current client can complete. Xquik keeps OAuth issuer, redirect, resource, and Proof Key for Code Exchange (PKCE) validation enabled for every client. | Client | API MCP authentication today | Registration and behavior | | -------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------- | | [Claude Code](https://docs.anthropic.com/en/docs/claude-code/mcp) | OAuth 2.1 | Uses Client ID Metadata Documents (CIMD), secure token storage, and automatic refresh | | [OpenCode](https://opencode.ai/docs/mcp-servers/) | OAuth 2.1 | Uses Dynamic Client Registration (DCR) and refreshes tokens | | [Gemini CLI](https://geminicli.com/docs/tools/mcp-server/) | OAuth 2.1 | Uses automatic OAuth discovery and DCR; Streamable HTTP configuration uses `httpUrl` | | [Cursor](https://docs.cursor.com/context/model-context-protocol) | OAuth 2.1 | Supports remote MCP OAuth and `cursor-agent mcp login` | | [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers) | OAuth 2.1 | Uses the browser authorization code flow and DCR | | [Cline](https://docs.cline.bot/cli/cli-reference) | OAuth 2.1 | Completes OAuth from its MCP configuration flow | | [Qwen Code](https://github.com/QwenLM/qwen-code) | OAuth 2.1 | Uses DCR and its `httpUrl` Streamable HTTP field | | [Codex](https://learn.chatgpt.com/docs/extend/mcp) | Environment-backed API key | Affected releases can discard the required RFC 9207 `iss` callback value | | [Goose](https://goose-docs.ai/docs/getting-started/using-extensions/) | Environment-backed API key | Affected releases can discard the required RFC 9207 `iss` callback value | | [Roo Code](https://github.com/RooCodeInc/Roo-Code) | Environment-backed API key | Roo Code's archived final release has Streamable HTTP but no MCP OAuth provider | | [Pi](https://github.com/earendil-works/pi/tree/main/packages/coding-agent) | No native MCP path | Pi requires a separately installed and tested MCP adapter | Clients that ignore the optional RFC 9207 `iss` response parameter can still complete OAuth. Affected Codex and Goose releases instead require the parameter after discarding it, so retrying OAuth cannot repair the callback. Xquik does not weaken issuer validation for those releases. ## Setup ### Web and terminal clients 1. Open [Claude Connectors](https://claude.ai/settings/connectors) or **Customize > Connectors**. 2. Select **+**, then **Add custom connector**. 3. Enter `https://xquik.com/mcp`. 4. Select **Add**. 5. In a chat, select **+ > Connectors**, enable Xquik, then select **Connect** and approve access. Leave the advanced client ID and client secret fields empty. Custom remote connectors require Pro, Max, Team, or Enterprise. On Team and Enterprise, an Owner or Primary Owner must add the connector first. Claude Desktop uses the same remote custom connectors as Claude.ai. Open **Customize > Connectors**, add `https://xquik.com/mcp`, then complete the browser authorization flow. Add the remote server: ```bash theme={null} claude mcp add --transport http xquik https://xquik.com/mcp ``` Run `/mcp` inside Claude Code, select `xquik`, then authenticate. 1. In ChatGPT on the web, open **Settings > Security and login**. Enable **Developer mode**. 2. Open [**Settings > Plugins**](https://chatgpt.com/plugins). Select **+**. 3. Enter `https://xquik.com/mcp`, then select **Create**. 4. Sign in to Xquik and approve access. Confirm the tool list. 5. Start a new chat. Select **+ > More**, then select Xquik. ChatGPT uses Xquik OAuth and cannot present a custom API key. Full MCP is in beta for Business and Enterprise/Edu workspaces. Pro supports read and fetch tools only. Link Xquik on the web first. The linked app then appears on mobile. Follow [OpenAI's current setup guide](https://developers.openai.com/apps-sdk/deploy/connect-chatgpt) when ChatGPT changes its labels. ### OpenAI Current Codex releases affected by [openai/codex#31573](https://github.com/openai/codex/issues/31573) must use the [Codex API-key fallback](#codex-api-key-fallback) below. Do not run `codex mcp login xquik` while that fallback is active. After your Codex release includes the upstream issuer fix, remove `bearer_token_env_var`, then add Xquik and complete OAuth: ```bash theme={null} codex mcp add xquik --url https://xquik.com/mcp codex mcp login xquik codex mcp list ``` Codex CLI, the IDE extension, and the ChatGPT desktop app share the same `config.toml` MCP configuration. Current affected releases use the [environment-backed API-key fallback](#codex-api-key-fallback) through the shared `config.toml`, then restart Codex Desktop. After your release includes the upstream fix, open **Settings > MCP servers**, add `https://xquik.com/mcp` as Streamable HTTP, select **Authenticate**, then restart. Current affected releases use the `bearer_token_env_var` configuration in [Codex API-key fallback](#codex-api-key-fallback). After your release includes the upstream fix, use this OAuth configuration in `~/.codex/config.toml` or a trusted project's `.codex/config.toml`: ```toml theme={null} [mcp_servers.xquik] url = "https://xquik.com/mcp" ``` Then run `codex mcp login xquik`. ### Codex API-key fallback Use an environment-backed API key if Codex reports `Authorization server response missing required issuer: expected https://xquik.com`: ```bash theme={null} export XQUIK_API_KEY="xq_your_api_key_here" ``` Add this configuration to `~/.codex/config.toml` or a trusted project's `.codex/config.toml`: ```toml theme={null} [mcp_servers.xquik] url = "https://xquik.com/mcp" bearer_token_env_var = "XQUIK_API_KEY" ``` Restart Codex, then run `codex mcp list`. Do not run `codex mcp login xquik` while using the bearer-token fallback. Never commit the key or place its value directly in `config.toml`. See [Codex OAuth issuer validation error](/guides/troubleshooting#codex-oauth-issuer-validation-error) for the client regression and recovery steps. Track the [upstream Codex issue](https://github.com/openai/codex/issues/31573) for a fixed release. ### Editor clients Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project): ```json theme={null} { "mcpServers": { "xquik": { "url": "https://xquik.com/mcp" } } } ``` Cursor starts OAuth when the server first returns `401`. You can also run `cursor-agent mcp login xquik`. Cursor currently lists MCP access on its paid Individual, Teams, and Enterprise plans. Add to `.vscode/mcp.json` (project) or use **MCP: Open User Configuration** (global): ```json theme={null} { "servers": { "xquik": { "type": "http", "url": "https://xquik.com/mcp" } } } ``` Start the server from the MCP view and follow the OAuth prompt. VS Code stores the resulting authentication state. Add to `~/.codeium/windsurf/mcp_config.json`: ```json theme={null} { "mcpServers": { "xquik": { "serverUrl": "https://xquik.com/mcp" } } } ``` Enable the server in **Windsurf Settings > Cascade > MCP Servers**, then complete OAuth. Enterprise users must enable MCP manually. Team policies may disable MCP or restrict servers to an allowlist. Add to `opencode.json`: ```json theme={null} { "mcp": { "xquik": { "type": "remote", "url": "https://xquik.com/mcp" } } } ``` Then run: ```bash theme={null} opencode mcp auth xquik opencode mcp list ``` ### Other terminal clients Add the remote server: ```bash theme={null} copilot mcp add xquik --type http --url https://xquik.com/mcp ``` If your installed build does not expose the noninteractive add flags, start Copilot CLI and run `/mcp add`. Enter `xquik`, choose **HTTP**, enter `https://xquik.com/mcp`, keep `*` for tools, then press **Ctrl+S**. Run `/mcp auth xquik` after the server appears. Enterprise policy may block servers outside the organization allowlist. Add the remote server: ```bash theme={null} gemini mcp add --transport http xquik https://xquik.com/mcp ``` Or add it to `~/.gemini/settings.json` for user scope or `.gemini/settings.json` for project scope: ```json theme={null} { "mcpServers": { "xquik": { "httpUrl": "https://xquik.com/mcp" } } } ``` Run `/mcp auth xquik` to complete OAuth. Run `cline mcp`, add a Streamable HTTP server, and enter `https://xquik.com/mcp`. Select **Authorize OAuth** when Cline reports that authentication is required. Enable encrypted token storage before adding Xquik: ```bash theme={null} export QWEN_CODE_FORCE_ENCRYPTED_FILE_STORAGE=true qwen mcp add --transport http xquik https://xquik.com/mcp ``` Start Qwen Code, open `/mcp`, then authorize `xquik`. Qwen Code still uses `httpUrl` for manual Streamable HTTP configuration: ```json theme={null} { "mcpServers": { "xquik": { "httpUrl": "https://xquik.com/mcp" } } } ``` ### Remaining API-key and adapter paths API-key fallback is client-specific. ChatGPT custom apps require OAuth and cannot present custom API keys. Codex uses the environment-backed `bearer_token_env_var` configuration above. For other clients, follow that client's documented secret-input or environment-variable syntax. Never copy a generic header example into an incompatible schema, place a literal key in a configuration file, or commit a key. Export your key, then add this entry to `~/.config/goose/config.yaml`: ```bash theme={null} export XQUIK_API_KEY="xq_your_api_key_here" ``` ```yaml theme={null} extensions: xquik: type: streamable_http name: xquik enabled: true uri: "https://xquik.com/mcp" headers: Authorization: "Bearer ${XQUIK_API_KEY}" env_keys: - XQUIK_API_KEY envs: {} ``` Goose substitutes the environment variable before sending the header. Its current OAuth callback has the same RFC 9207 issuer handling defect as Codex. Follow [Codex and Goose OAuth issuer validation](/guides/troubleshooting#codex-oauth-issuer-validation-error). Roo Code's archived final release supports API-key headers, not MCP OAuth. Add this to global `mcp_settings.json` or project `.roo/mcp.json`: ```json theme={null} { "mcpServers": { "xquik": { "type": "streamable-http", "url": "https://xquik.com/mcp", "headers": { "Authorization": "Bearer ${env:XQUIK_API_KEY}" } } } } ``` Export `XQUIK_API_KEY` before starting the editor. Do not place the key value in the JSON file. Pi's coding agent has no native MCP client. Install and audit a community MCP adapter before connecting Xquik, or call the [REST API](/api-reference/overview) from a Pi extension. Xquik does not claim native Pi compatibility. ## Example prompts Once connected, ask: **Monitoring & Events** * Start watching @elonmusk for new tweets and replies. * List the accounts I am currently monitoring. * Show monitored account activity from today. * Replay stored events for monitor mon\_123 using the last next\_cursor as cursor. * Stop tracking @elonmusk. **Search & Lookup** * Search recent X posts about TypeScript. * Find recent tweets from @vercel. * Read this tweet: `https://x.com/elonmusk/status/1893456789012345678` * Get metrics for this tweet: `https://x.com/vercel/status/1893704267862470862` **User Profiles & Follows** * Get @username follower count. * Read @openai profile bio. * Check whether @elonmusk follows @SpaceX. * Check whether @vercel and @nextjs follow each other. **Trends** * Show current X trends. * Show top trending topics in the US. * Check whether AI is trending today. **Radar & News** * Show current Radar trends. * Show current Reddit posts with text, links, media, and engagement signals. * Show top developer trends today. * Show startups ranked by available growth metrics. * Get technology topics from the last 12 hours. * Show popular knowledge topics right now. * Show regional trends for a selected region. * Find trending tech news and draft a tweet about one item. **Extractions** * Pull all replies to this tweet: `https://x.com/elonmusk/status/1893456789012345678` * List users who retweeted this tweet: `https://x.com/vercel/status/1893704267862470862` * Estimate the cost to extract all followers of @elonmusk. * Get quote tweets for this post: `https://x.com/openai/status/1893456789012345678` * Extract the full thread for this tweet: `https://x.com/elonmusk/status/1893704267862470862` **Giveaways** * Pick 3 random winners from this tweet: `https://x.com/example_user/status/1893456789012345678` * Run a giveaway draw where participants must have retweeted and have at least 100 followers. * Show the results of my last giveaway draw. **Webhooks** * Set up a webhook at `https://my-server.com/events` for new tweets. * List configured webhook endpoints. * Remove the webhook pointing to my old server. **Tweet Composition** * Write a casual launch tweet for my new product. * Research a fresh angle from Compose's Radar recommendations. * Optimize the draft for engagement. * Score this draft: Just shipped v2.0 of our API. What do you think? * Improve this tweet to get more replies. **Style Analysis & Drafts** * Analyze how @elonmusk tweets. * Compare @vercel and @nextjs tweeting styles. * Show cached tweet performance. * Save this tweet draft for later. * Show all saved drafts. * Set my X account to @myusername. **X Write Actions** * Post a tweet saying: Just shipped v2.0! * Like this tweet: `https://x.com/vercel/status/1893704267862470862` * Retweet this: `https://x.com/openai/status/1893456789012345678` * Follow @vercel from my connected account. * Send a DM to user ID 44196397 saying hello. * Post a tweet saying: New feature! Use public image URL `https://example.com/launch.png`. **Account & Usage** * Show my plan and month-to-date usage. * Check whether I have enough budget left for a large extraction. ## Framework guides Build agents with Xquik's MCP tools in your preferred framework: Python agents with LangChain + LangGraph Multi-agent crews with CrewAI Type-safe agents with Pydantic AI Multi-agent assistants with Google ADK TypeScript agents with Mastra Python agents with Microsoft Agent Framework Move an existing Composio workflow to Xquik ## AI agent skill The [Xquik Skill](https://github.com/Xquik-dev/x-twitter-scraper) gives AI coding agents deep knowledge of the Xquik API without requiring an MCP connection. Install it to let your agent write API integrations, set up webhooks, and configure MCP connections using Xquik best practices. Works with Claude Code, Cursor, GitHub Copilot, Codex, Windsurf, VS Code, Gemini CLI, and other Skill-capable agents. It covers MCP tools and 128 REST API operations. ```bash theme={null} npx skills add Xquik-dev/x-twitter-scraper ``` # MCP Tools for Tweet Search, Followers & X Actions Source: https://docs.xquik.com/mcp/tools Choose API MCP tools for tweet search, profile lookup, follower exports, monitors, webhooks, account actions, pagination, and sandbox work. See tool examples.
For the complete documentation index, see llms.txt.
Xquik API MCP v2.6.0 exposes a scoped catalog through 2 tools. `explore` searches. `xquik` runs authenticated calls. Full credentials see 120 catalog routes. Of these, 119 return JSON or text through MCP. Active guest `paid_reads` keys see exactly 33 GET routes. Modern clients negotiate MCP `2026-07-28` through `server/discover`. Current SDKs add request metadata and HTTP headers automatically. See [MCP 2026-07-28](/mcp/overview#mcp-2026-07-28). | Agent task | MCP call | Durable output | | --------------------- | ------------------------------------------ | ------------------------------------------------------------- | | Discover tweet routes | `explore` over `spec.endpoints` | Save the chosen method, path, parameters, and cost. | | Search tweets | `xquik.request('/api/v1/x/tweets/search')` | Preserve tweet IDs, authors, timestamps, and `next_cursor`. | | Export followers | `xquik.request('/api/v1/extractions')` | Persist the extraction ID before polling or exporting. | | Extract replies | `xquik.request('/api/v1/extractions')` | Store the target tweet ID and extraction ID together. | | Monitor keywords | `xquik.request('/api/v1/monitors')` | Save the monitor ID before creating its webhook. | | Replay events | `xquik.request('/api/v1/events')` | Keep event IDs and the next page cursor. | | Run write actions | `xquik.request()` with the write route | Store `write_action_id`, `status_url`, and `charged_credits`. | ## explore Search the authenticated API catalog. `explore` makes no network calls and uses no credits. Full credentials search 120 routes. Guest keys search 33 GET routes. **Input:** Pass `code` as an async arrow function. It is required and can be up to 10,000 characters. The function runs against `spec.endpoints` so agents can filter endpoint paths, parameters, categories, costs, and response shapes before making a live call. **Sandbox API:** ```typescript theme={null} interface EndpointInfo { method: string; path: string; summary: string; category: string; // account, composition, credits, extraction, media, monitoring, support, twitter, x-accounts, x-write free: boolean; injectedHeaders?: string[]; parameters?: Array<{ name: string; in: 'query' | 'path' | 'body'; required: boolean; type: string; description: string; }>; responseShape?: string; } declare const spec: { endpoints: EndpointInfo[] }; ``` **Examples:** > Find all free endpoints ```javascript theme={null} async () => { return spec.endpoints.filter(e => e.free); } ``` > Find endpoints by category ```javascript theme={null} async () => { return spec.endpoints.filter(e => e.category === 'composition'); } ``` > Search by keyword ```javascript theme={null} async () => { return spec.endpoints.filter(e => e.summary.toLowerCase().includes('tweet')); } ``` ## xquik Execute API calls allowed by the authenticated credential. Full account keys and OAuth tokens keep their existing account capabilities. Guest keys can execute only the 33 eligible GET reads. Authentication and required idempotency headers are injected automatically. **Input:** Pass `code` as an async arrow function. It is required and can be up to 10,000 characters. The function can call `xquik.request(path, { method, body, query })` with authentication and required idempotency headers injected automatically. **Sandbox API:** ```typescript theme={null} declare const xquik: { request(path: string, options?: { method?: string; // default: 'GET' body?: unknown; query?: Record; }): Promise; }; declare const spec: { endpoints: EndpointInfo[] }; ``` **Response contract:** `xquik.request()` sends the normalized contract automatically. Hosted MCP injects a unique `Idempotency-Key` for routes that require it. The sandbox reuses that key for bounded transient retries. After an unresolved write failure, verify state. Start a new attempt only when `safe_to_retry` is true. Responses use snake\_case fields, date-time fields as Unix seconds, and structured error objects. A default REST `createdAt` field becomes `created`, while fields such as `publishedAt` become `published_at`. MCP preserves every safe field that X supplies. Optional fields stay absent. See [Read Data Richness](/guides/tweet-profile-api-fields) for the REST field map. List and search responses use `has_more` and `next_cursor`, even when a REST page shows `has_next_page` or `hasMore`. Pass `next_cursor` as `cursor` for tweet, profile, follower, reply, timeline, community, and list pages. Draws, extractions, and events also use `cursor`. Use `after` for `/api/v1/radar`. Use `afterCursor` for `/api/v1/drafts`. Omit `mode` for tweet search, replies, followers, following, and verified followers. Those operations use automatic maximum coverage. Pass `next_cursor` back unchanged. Use `mode=standard` only for legacy pagination. Continue through empty filtered pages while `has_more` is true and the cursor advances. Stop when you reach the requested total or `has_more` becomes false. Treat a missing or repeated `next_cursor` while `has_more` is true as stalled pagination and return the partial count plus a clear stop reason. For advanced nested-reply diagnostics, call `/api/v1/x/tweets//replies?mode=complete&limit=25000`. Complete mode combines timelines, rankings, cursors, hidden branches, and search. Direct replies match `inReplyToId`. Keep `nested_replies` separate. Trust `diagnostic.complete`. HTTP 424 `replies_incomplete` preserves rows. Inspect `coveragePercentage`, strategy results, cursor failures, missing modules, and `recommendedFallback`. Disclose that reply coverage depends on X. Errors use `error.type`, `error.code`, and `error.message`, with fields such as `error.retryable` or `error.retry_after` when available. Dependency failures use HTTP `424` in this contract. Fix validation errors before retrying, and respect `retry_after` on `429`. Write and media responses also use the MCP-normalized snake\_case contract. Read `tweet_id`, `write_action_id`, `charged_credits`, `media_id`, `media_url`, and `message_id` from `xquik.request()` results. REST and generated SDK pages may show camelCase fields such as `tweetId`, `writeActionId`, `chargedCredits`, `mediaId`, and `messageId`; keep MCP agents on snake\_case when reading tool results. Tool output is limited to 24,000 characters. If a result includes `[TRUNCATED]`, request fewer rows, project only needed fields, or aggregate inside the sandbox. Use REST, SDKs, or extraction exports when a workflow must persist every full row. ### Scope and unavailable operations The REST contract documents 128 operations. Full credentials expose 120 MCP catalog routes. Eight credential and session operations stay outside: * Create, list, or revoke account API keys * Charge a saved payment method through quick top-up * Open the session-based account top-up redirect route * Create, poll, or top up a guest wallet The catalog includes private support attachment downloads. MCP rejects their binary responses. Use REST for those downloads. The other 119 routes return JSON or text. The guest wallet routes are direct REST only. Never call `/api/v1/guest-wallets`, `/api/v1/guest-wallets/topups`, or `/api/v1/guest-wallets/status` through `xquik.request()`. Follow the [accountless guest wallet guide](/guides/guest-wallets) outside MCP. A guest `paid_reads` key receives a separate 33-operation catalog. Every entry is an [eligible paid-read GET route](/guides/guest-wallets#eligible-paid-read-routes). The sandbox cannot execute mutations or noneligible routes. A `402` creates no checkout. Report its `payment_options`, ask the user to choose an amount and option, then wait for explicit confirmation. Full account MCP sessions may call only an advertised account checkout action present in their catalog. Guest wallet creation and top-up remain direct REST after confirmation. **Workflow Examples:** > Build and check a post draft (3-step, free) ```javascript theme={null} async () => { // Step 1: Get editorial rules, questions, and Radar recommendations. const compose = await xquik.request('/api/v1/compose', { method: 'POST', body: { step: 'compose', topic: 'AI agents' } }); return compose; // Use radar_recommendations when fresh context helps. // Step 2: Pass selected facts through additionalContext when refining. // Step 3: After drafting: { step: 'score', draft } // The score step runs 9 deterministic editorial checks. } ``` > Save a writing style from screenshots (free) ```javascript theme={null} async () => { // When a user shares tweet screenshots, extract the texts and save as a style. // This lets free users clone any writing voice without a subscription. return xquik.request('/api/v1/styles/elonmusk', { method: 'PUT', body: { label: 'Elon Musk style', tweets: [ { text: 'The most entertaining outcome is the most likely' }, { text: 'Mars, here we come!!' } ] } }); // Then compose with: POST /api/v1/compose { step: 'compose', topic: '...', styleUsername: 'elonmusk' } } ``` > Browse trending news from radar (free) ```javascript theme={null} async () => { return xquik.request('/api/v1/radar'); } ``` Reddit Radar items can expose post text, links, media, and engagement data. Startup growth items can expose reported metrics, company details, and founder X usernames. MCP returns multiword fields in snake\_case. Examples include `source_format`, `estimated_upvotes`, and `x_handle`. Comment bodies are not included. > Radar + Style + Compose combined (free) ```javascript theme={null} async () => { const [radar, styles] = await Promise.all([ xquik.request('/api/v1/radar'), xquik.request('/api/v1/styles'), ]); return { radar, styles }; } ``` > Analyze a user's writing style ```javascript theme={null} async () => { // Returns cached style if available (free) // Auto-refreshes from X if cache older than 7 days (credits required) return xquik.request('/api/v1/styles', { method: 'POST', body: { username: 'elonmusk' } }); } ``` > Summarize up to 100 tweets with guarded pagination (credits required) Use `q` for keywords and X search operators, or pass a plain Tweet ID or X status URL when the agent receives a single stored link. ```javascript theme={null} async () => { const target = 100; const query = 'from:username giveaway'; const seenIds = new Set(); const seenCursors = new Set(); const sampleTweets = []; let cursor; let hasMore = true; let nextCursor = ''; let stopReason = 'page_cap'; for (let pageNumber = 0; pageNumber < 10 && seenIds.size < target && hasMore; pageNumber += 1) { const page = await xquik.request('/api/v1/x/tweets/search', { query: { q: query, limit: String(Math.min(200, target - seenIds.size)), ...(cursor ? { cursor } : {}) } }); for (const tweet of page.tweets) { if (seenIds.has(tweet.id) || seenIds.size >= target) continue; seenIds.add(tweet.id); if (sampleTweets.length < 20) { sampleTweets.push({ tweet_id: tweet.id, text_excerpt: tweet.text?.slice(0, 120) ?? null, author_id: tweet.author?.id ?? null, author_username: tweet.author?.username ?? null, created: tweet['created'] ?? null, like_count: tweet.like_count ?? null, view_count: tweet.view_count ?? null }); } } hasMore = Boolean(page.has_more); nextCursor = page.next_cursor ?? ''; if (!hasMore) { stopReason = 'exhausted'; break; } if (!nextCursor || nextCursor === cursor || seenCursors.has(nextCursor)) { stopReason = 'cursor_stalled'; break; } seenCursors.add(nextCursor); cursor = nextCursor; } return { source: 'xquik_mcp', job: 'tweet_search', query, rows_seen: seenIds.size, sample_tweets: sampleTweets, has_more: hasMore, next_cursor: nextCursor, stop_reason: seenIds.size >= target ? 'requested_total' : stopReason }; } ``` > Summarize up to 100 followers with guarded pagination (credits required) ```javascript theme={null} async () => { const target = 100; const sourceUser = 'username'; const seenIds = new Set(); const seenCursors = new Set(); const sampleProfiles = []; let verifiedCount = 0; let cursor; let hasMore = true; let nextCursor = ''; let stopReason = 'page_cap'; for (let pageNumber = 0; pageNumber < 10 && seenIds.size < target && hasMore; pageNumber += 1) { const page = await xquik.request(`/api/v1/x/users/${sourceUser}/followers`, { query: { pageSize: String(Math.max(20, Math.min(200, target - seenIds.size))), ...(cursor ? { cursor } : {}) } }); for (const user of page.users) { if (seenIds.has(user.id) || seenIds.size >= target) continue; seenIds.add(user.id); if (user.verified === true) verifiedCount += 1; if (sampleProfiles.length < 25) { sampleProfiles.push({ user_id: user.id, username: user.username, name: user.name ?? null, followers: user.followers ?? null, verified: user.verified ?? null, profile_picture: user.profile_picture ?? null }); } } hasMore = Boolean(page.has_more); nextCursor = page.next_cursor ?? ''; if (!hasMore) { stopReason = 'exhausted'; break; } if (!nextCursor || nextCursor === cursor || seenCursors.has(nextCursor)) { stopReason = 'cursor_stalled'; break; } seenCursors.add(nextCursor); cursor = nextCursor; } return { source: 'xquik_mcp', job: 'follower_export', source_user: sourceUser, rows_seen: seenIds.size, verified_count: verifiedCount, sample_profiles: sampleProfiles, has_more: hasMore, next_cursor: nextCursor, stop_reason: seenIds.size >= target ? 'requested_total' : stopReason }; } ``` > Scrape tweet replies to CSV, JSON, or XLSX (credits required) ```javascript theme={null} async () => { const body = { toolType: 'reply_extractor', targetTweetId: '1893704267862470862', resultsLimit: 500 }; const estimate = await xquik.request('/api/v1/extractions/estimate', { method: 'POST', body }); if (estimate.allowed === false) { return { source: 'xquik_mcp', job: 'reply_extraction', status: 'blocked', error: estimate.error, credits_required: estimate.credits_required, credits_available: estimate.credits_available }; } const extraction = await xquik.request('/api/v1/extractions', { method: 'POST', body }); return { source: 'xquik_mcp', job: 'reply_extraction', extraction_id: extraction.id, status: extraction.status, target_tweet_id: body.targetTweetId, results_limit: body.resultsLimit, estimated_results: estimate.estimated_results, credits_required: estimate.credits_required, poll: `/api/v1/extractions/${extraction.id}`, export_csv: `/api/v1/extractions/${extraction.id}/export?format=csv`, export_json: `/api/v1/extractions/${extraction.id}/export?format=json`, export_xlsx: `/api/v1/extractions/${extraction.id}/export?format=xlsx` }; } ``` > Post a tweet or reply with public media URLs (credits required) Hosted MCP injects the required `Idempotency-Key`. Direct REST callers must supply it themselves. ```javascript theme={null} async () => { const body = { account: 'myxhandle', text: 'Launch media is ready', reply_to_tweet_id: '1893456789012345678', media: ['https://example.com/product-demo.mp4'] }; const result = await xquik.request('/api/v1/x/tweets', { method: 'POST', body }); return { source: 'xquik_mcp', job: 'tweet_write', status: result.status, terminal: result.terminal, safe_to_retry: result.safe_to_retry, write_action_id: result.id, request_hash: result.request.hash, tweet_id: result.result?.id ?? result.tweet_id ?? null, poll: result.terminal ? null : result.status_url, account: body.account, reply_to_tweet_id: body.reply_to_tweet_id, media: body.media, charged: result.billing.charged, charged_credits: result.billing.charged_credits }; } ``` > Upload media for a DM (credits required) ```javascript theme={null} async () => { const account = 'myxhandle'; const user_id = '44196397'; const source_url = 'https://example.com/image.png'; const media = await xquik.request('/api/v1/x/media', { method: 'POST', body: { account, url: source_url } }); const dm = await xquik.request(`/api/v1/x/dm/${user_id}`, { method: 'POST', body: { account, text: 'Here is the asset', media_ids: [media.media_id] } }); return { source: 'xquik_mcp', job: 'dm_media', status: 'sent', user_id, account, source_url, media_id: media.media_id, media_url: media.media_url, message_id: dm.message_id }; } ``` Store `message_id` with the uploaded `media_id`. Keep full DM bodies out of shared MCP outputs; return IDs, status, media references, and source filenames instead. Leave `reply_to_message_id` unset because the DM send endpoint rejects reply threading. > Download media and get gallery link (credits required) ```javascript theme={null} async () => { const tweet_input = '1234567890'; const download = await xquik.request('/api/v1/x/media/download', { method: 'POST', body: { tweetInput: tweet_input } }); return { source: 'xquik_mcp', job: 'media_download', mode: 'single', tweet_input, tweet_id: download.tweet_id, gallery_url: download.gallery_url, cache_hit: download.cache_hit, note: 'Store gallery_url as the saved media gallery. It is not an uploaded media_id for DMs.' }; } ``` > Bulk download: search + download combined ```javascript theme={null} async () => { const query = 'from:berktavsan has:videos'; const search = await xquik.request('/api/v1/x/tweets/search', { query: { q: query } }); if (!search.tweets?.length) { return { source: 'xquik_mcp', job: 'bulk_media_download', status: 'empty', query }; } const tweetIds = search.tweets.map(t => t.id).slice(0, 50); const download = await xquik.request('/api/v1/x/media/download', { method: 'POST', body: { tweetIds } }); return { source: 'xquik_mcp', job: 'bulk_media_download', status: 'ready', query, tweet_ids: tweetIds, gallery_url: download.gallery_url, total_tweets: download.total_tweets, total_media: download.total_media }; } ``` > Monitor a user + create webhook (monitor creation requires credits, webhook is free) ```javascript theme={null} async () => { const monitor = await xquik.request('/api/v1/monitors', { method: 'POST', body: { username: 'elonmusk', eventTypes: ['tweet.new', 'tweet.reply'] } }); const webhook = await xquik.request('/api/v1/webhooks', { method: 'POST', body: { url: 'https://example.com/hook', eventTypes: ['tweet.new', 'tweet.reply'] } }); const test = await xquik.request(`/api/v1/webhooks/${webhook.id}/test`, { method: 'POST' }); return { monitor_id: monitor.id, event_types: monitor.event_types, next_billing_at: monitor.next_billing_at, webhook_id: webhook.id, webhook_url: webhook.url, save_secret_once: 'Store webhook.secret for X-Xquik-Signature verification; do not print it in logs.', idempotency_keys: ['deliveryId', 'streamEventId'], delivery_status: `/api/v1/webhooks/${webhook.id}/deliveries`, test }; } ``` > Poll stored monitor events (free) ```javascript theme={null} async () => { const monitor_id = 'mon_123'; const event_type = 'tweet.new'; const page = await xquik.request('/api/v1/events', { query: { monitorId: monitor_id, eventType: event_type } }); return { source: 'xquik_mcp', job: 'monitor_event_poll', monitor_id, event_type, rows: page.events.map(event => ({ event_id: event.id, type: event.type, username: event.username ?? null, query: event.query ?? null, monitor_id: event.monitor_id, monitor_type: event.monitor_type, occurred_at: event.occurred_at, data: event.data })), has_more: page.has_more, next_cursor: page.next_cursor, next_query: page.next_cursor ? { monitorId: monitor_id, eventType: event_type, cursor: page.next_cursor } : null }; } ``` > Run an extraction with a resumable handoff (credits required) ```javascript theme={null} async () => { const body = { toolType: 'tweet_search_extractor', searchQuery: 'launch announcement', resultsLimit: 500 }; const estimate = await xquik.request('/api/v1/extractions/estimate', { method: 'POST', body }); if (estimate.allowed === false) { return { source: 'xquik_mcp', job: 'tweet_search_extraction', status: 'blocked', error: estimate.error, credits_required: estimate.credits_required, credits_available: estimate.credits_available }; } const job = await xquik.request('/api/v1/extractions', { method: 'POST', body }); return { source: 'xquik_mcp', job: 'tweet_search_extraction', extraction_id: job.id, tool_type: job.tool_type, status: job.status, query: body.searchQuery, results_limit: body.resultsLimit, estimated_results: estimate.estimated_results, credits_required: estimate.credits_required, poll: `/api/v1/extractions/${job.id}`, export_after_complete: `/api/v1/extractions/${job.id}/export?format=json` }; } ``` ## Agent handoff patterns MCP returns JSON. Use extraction export endpoints when you need Xquik to generate CSV, JSON, XLSX, Markdown, or PDF files. For agent queues, CRMs, and warehouses, return a small object with the original job, the route used, normalized rows or IDs to store, and the next cursor or write action to poll. Avoid returning raw `tweets` or `users` pages when the next agent or worker needs durable handoff rows. Call `GET /api/v1/x/tweets/search` with keywords, operators, a Tweet ID, or an X status URL in `q`. Valid time bounds apply to every page. The start is inclusive and the end is exclusive. Store `tweets[].id`, `tweets[].text`, `tweets[].author`, `tweets[].created`, `has_more`, `next_cursor`, and the original `q`. Cost: 1 credit per tweet returned. Call `POST /api/v1/extractions/estimate`, then `POST /api/v1/extractions` with `reply_extractor` and `targetTweetId`. Poll `GET /api/v1/extractions/{id}`, export CSV/JSON/XLSX with `GET /api/v1/extractions/{id}/export`, and store reply rows plus `has_more` and `next_cursor`. Cost: 1 credit per reply extracted or returned. Call `GET /api/v1/x/users/{id}/followers` or `POST /api/v1/extractions` with `follower_explorer`. Store `users[].id`, `users[].username`, `users[].name`, `users[].followers`, `has_more`, and `next_cursor`. Cost: 1 credit per follower returned or extracted. Call `POST /api/v1/x/tweets` with `media: ["https://..."]`. Store `tweet_id` or `write_action_id`, `reply_to_tweet_id`, `account`, `charged_credits`, and the original `media` URLs. Cost: 30 credits text-only, plus 2 credits per started MB across attached media. Call `POST /api/v1/x/media`, then `POST /api/v1/x/dm/{userId}` with one `media_ids` value. Store `media_id`, `media_url`, `message_id`, `user_id`, `account`, and source URL or filename. Keep full DM bodies out of shared outputs and leave `reply_to_message_id` unset. Cost: 10 credits per media upload plus 10 credits per DM send. Call `POST /api/v1/x/tweets`, then `GET /api/v1/x/write-actions/{id}` when pending. Store `tweet_id`, `reply_to_tweet_id`, `write_action_id`, `status`, `charged`, `charged_credits`, and `media`. Cost: 30 credits text-only, plus 2 credits per started MB across attached media. Call `POST /api/v1/monitors` or `POST /api/v1/monitors/keywords`, then `POST /api/v1/webhooks`. Store `monitor.id`, `event_types`, `next_billing_at`, `webhook.id`, webhook URL, and the one-time `webhook.secret`; run `POST /api/v1/webhooks/{id}/test` before routing production events. Verify `X-Xquik-Signature`, de-dupe production payloads with `deliveryId` and `streamEventId`, and inspect `GET /api/v1/webhooks/{id}/deliveries` for retry status rows. Each payload contains one monitor event, so process multiple POSTs when one check catches multiple new matching tweets. Cost: 21 credits per active monitor-hour; webhook delivery is included. Call `GET /api/v1/events` when a receiver missed webhook delivery or a downstream queue needs replay. Store `event_id`, `type`, `monitor_id`, `monitor_type`, `occurred_at`, `has_more`, and `next_cursor`. Use `cursor` for the next page. Do not upload media before posting tweets or replies when the media is already public. `POST /api/v1/x/tweets` rejects `media_ids` with `400 unsupported_field`; pass up to 4 public image URLs or exactly 1 public MP4 video URL up to 100 MB in `media` instead. Reserve uploaded `media_id` values for direct messages. ```javascript theme={null} async () => { const page = await xquik.request('/api/v1/x/tweets/search', { query: { q: 'from:username giveaway', limit: '50' } }); return { source: 'xquik_mcp', job: 'tweet_search', query: 'from:username giveaway', rows: page.tweets.map(tweet => ({ tweet_id: tweet.id, text: tweet.text, author: tweet.author, created: tweet['created'], url: tweet.url })), has_more: page.has_more, next_cursor: page.next_cursor }; } ``` > Look up known tweet IDs `GET /api/v1/x/tweets` is available in both the full and `paid_reads` catalogs. Send at most 100 tweet IDs. ```javascript theme={null} async () => { const page = await xquik.request('/api/v1/x/tweets', { query: { ids: '1893456789012345678,1893456789012345679' } }); return { tweets: page.tweets.map(tweet => ({ tweet_id: tweet.id, text: tweet.text, author_username: tweet.author?.username ?? null })) }; } ``` > Subscribe (free, returns checkout or billing portal URL) Run this mutation only after the user explicitly asks to subscribe or open billing. ```javascript theme={null} async () => { return xquik.request('/api/v1/subscribe', { method: 'POST' }); } ``` ## API endpoints The REST API documents 128 operations. The full MCP catalog exposes 120 across 10 categories: 20 MCP operations across `account`, `composition`, and `credits`: account info, subscribe, X identity, compose, styles, drafts, radar, balance checks, checkout creation, and checkout status. 10 operations across `extraction` and `media`: giveaway draws, extraction jobs, estimates, exports, and media download. 19 operations in `monitoring`: account monitors, keyword monitors, stored events, webhooks, deliveries, and test delivery. 6 operations in `support`: create, list, read, reply, close, and download attachments. 38 operations in `twitter`: batch and single tweet lookup, tweet search, article lookup, user lookup, follow checks, trends, bookmarks, notifications, timeline, DM history, likes, media, followers, replies, communities, and lists. 27 operations across `x-accounts` and `x-write`: connect accounts, resolve challenges, post tweets, like, retweet, follow, remove followers, send DMs, upload media, update profiles, and manage communities. Use `explore` to browse all 120 catalog routes and their response shapes. With a guest `paid_reads` key, `explore` and `xquik` expose exactly 33 `twitter` GET operations. Use the [guest paid-read route inventory](/guides/guest-wallets#eligible-paid-read-routes) as the public route list. ## Cost summary `explore` is free. Use it to find endpoints, parameters, and response shapes before making API calls. Compose, cached styles, drafts, radar, subscribe, account, support, credits, X account management, webhooks, stored monitors, stored events, and existing extraction or draw reads are free. Tweet search, user lookup, follow checks, media download, trends, extraction creation, and draw creation are metered. Active monitors cost 21 credits per monitor-hour. Creating one requires enough available credits. Tweet, reply, like, retweet, follow, DM, profile, community, and media upload writes are metered. Fresh style analysis after the 7-day cache window requires enough available credits. Never combine free and paid endpoints in a single `Promise.all`. A 402 error on one call kills all results. Call free endpoints first, then paid ones separately. ## Error handling * **402 / `no_subscription` / `subscription_inactive`**: Report the billing state and available account actions. Existing available credits can still fund metered calls. Ask the user to choose and confirm before calling `POST /api/v1/subscribe`. * **402 / `no_credits` / `insufficient_credits`**: Report `payment_options`. Full account sessions may create account checkout after confirmation. Guest sessions may explain the direct REST top-up flow, but MCP cannot execute it. * **429 / `rate_limit_exceeded`**: Respect `error.retry_after`, then retry safe reads with backoff. * **424 dependency errors**: Report `error.code`, preserve partial aggregates, and retry only when `error.retryable` allows it. * **Validation errors**: Fix the path, query, or body before retrying. The MCP server never starts subscriptions, checkout, top-up, or other billing mutations in response to an API error. Guest credential routes are never executable through MCP. # Machine Payments Protocol for X API | Billing API Source: https://docs.xquik.com/mpp/machine-payments-protocol Use HTTP 402 and Tempo USDC for anonymous fixed-price tweet, user, follower, trend, Radar, health, and pricing requests. Includes payment and receipt examples.
For the complete documentation index, see llms.txt.
The [Machine Payments Protocol](https://mpp.dev) (MPP) is an open standard for machine-to-machine payments over HTTP. Xquik accepts direct MPP payments on 7 fixed-price read operations. You can call them without an account, API key, or subscription. ## How it works MPP uses HTTP 402 (Payment Required) as a payment challenge. When you call a direct MPP operation without authentication, the server returns an `application/problem+json` response and a `WWW-Authenticate: Payment` header. Your client pays through Tempo (USDC), then retries the request with a payment credential. ```text theme={null} Client Xquik │ │ │ 1. GET /api/v1/x/tweets/123 │ │──────────────────────────────────▶│ │ │ │ 2. 402 Payment Required │ │ WWW-Authenticate: Payment │ │ (challenge with amount, etc.) │ │◀──────────────────────────────────│ │ │ │ 3. Pay via Tempo (USDC) │ │ (off-band) │ │ │ │ 4. Retry with credential │ │ Authorization: Payment proof │ │──────────────────────────────────▶│ │ │ │ 5. HTTP response + │ │ Payment-Receipt │ │◀──────────────────────────────────│ ``` Every response after an accepted payment includes `Payment-Receipt`, including non-2xx responses. The receipt confirms settlement, not application success. Challenges and rejected payment credentials do not include a receipt. The same `402` body also advertises an optional accountless guest wallet. The MPP challenge remains unchanged. A failed read creates no checkout. Create a hosted checkout only after the user confirms an amount. No account, API key, or subscription is required. The payment credential authenticates the retried request. ## Payment method MPP payments on Xquik use **Tempo** with USDC, a dollar-pegged stablecoin. Tempo provides the settlement method for these fixed charges. You need a funded Tempo USDC wallet. See the [MPP quickstart](/mpp/quickstart) for setup instructions. ## Discovery MPP discovery metadata is available at: ```text theme={null} https://xquik.com/.well-known/mpp.json ``` The document points clients to the live OpenAPI spec and MPP overview page. ## Payment intent All direct MPP operations use the `charge` intent. Each accepted payment settles the fixed price for one request. Xquik does not advertise direct MPP session pricing for result-sized pages. ## Eligible endpoints | Endpoint | Method | Price | Intent | | --------------------------------- | ------ | -------------------- | ------ | | `/api/v1/x/tweets/{id}` | GET | USD 0.00015 per call | charge | | `/api/v1/x/users/{id}` | GET | USD 0.00015 per call | charge | | `/api/v1/x/followers/check` | GET | USD 0.00075 per call | charge | | `/api/v1/x/articles/{tweetId}` | GET | USD 0.00075 per call | charge | | `/api/v1/trends` | GET | USD 0.00045 per call | charge | | `/api/v1/x/trends` | GET | USD 0.00045 per call | charge | | `/api/v1/x/communities/{id}/info` | GET | USD 0.00015 per call | charge | An accountless guest wallet covers 33 prepaid GET routes, including these 7. See the [guest paid-read route inventory](/guides/guest-wallets#eligible-paid-read-routes). Direct MPP payment credentials and guest keys cannot access monitors, webhooks, extractions, draws, writes, account routes, or account credential routes. API MCP cannot execute guest wallet credential routes. ## MPP vs subscription
Compare MPP, guest wallet, and account access.
Capability MPP Guest wallet Account
Account required No No Yes
Credential Per-request payment credential paid\_reads API key Full API key or OAuth token
Payment Per request via Tempo (USDC) $10-$250 USD hosted checkout Available account credits; plans add monthly credits
Available endpoints 7 fixed-price GET operations 33 prepaid GET routes Full account API
Write actions Not available Not available Available with a connected X account
Monitors, webhooks, extractions, and draws Not available Not available Available
Best for Machine-paid requests Prepaid reads without an account Full platform workflows
## Next steps Make your first pay-per-use API call. All authentication methods including MPP headers. Subscription, guest wallet, and direct MPP pricing. Prepay 33 accountless reads with one scoped API key. # MPP Quickstart for Pay-per-use X API Requests Source: https://docs.xquik.com/mpp/quickstart Make an anonymous pay-per-use tweet, profile, follower, trend, or Radar request with HTTP 402, Tempo USDC, and a payment receipt. See payment examples.
For the complete documentation index, see llms.txt.
Call 7 fixed-price Xquik operations without an account or subscription. Pay per request with Tempo (USDC) using the `mppx` SDK. ## Step 1: Install the SDK ```bash theme={null} npm i mppx viem ``` The `mppx` package provides both client and server utilities. You only need the client. `viem` is required for wallet account management. ## Step 2: Set up a Tempo wallet You need a Tempo wallet funded with USDC, plus its raw hex private key for the `mppx` client. **Path A - CLI only (recommended for agents)** ```bash theme={null} mppx account create # generates a local keychain-backed account mppx account export # prints the hex private key (0x...) ``` Fund the account's address with USDC on Tempo, then copy the exported key into `TEMPO_PRIVATE_KEY`. **Path B - Web wallet as a USDC source** Use [wallet.tempo.xyz/welcome](https://wallet.tempo.xyz/welcome) as a hosted UI to buy & send USDC. Run `mppx account create` to mint a local account, copy its address, send USDC from the web wallet to that address, then run `mppx account export` to get the hex key. The web wallet cannot be imported into `mppx`. It is a funding source, not a key source. > **Warning:** Store your private key securely. Never commit it to version control. Use environment variables. ## Step 3: Make a charge request Look up a single tweet. The SDK intercepts 402 responses, pays via Tempo, and retries automatically. ```typescript theme={null} import { Mppx, tempo } from "mppx/client"; import { privateKeyToAccount } from "viem/accounts"; // Configure the MPP client. This patches global fetch // to automatically handle 402 Payment Required challenges Mppx.create({ methods: [ tempo({ account: privateKeyToAccount(process.env.TEMPO_PRIVATE_KEY as `0x${string}`), }), ], }); // Now any fetch to an MPP-enabled endpoint auto-pays const response = await fetch( "https://xquik.com/api/v1/x/tweets/1893456789012345678" ); const data = await response.json(); console.log(data.tweet.text); ``` `Mppx.create()` patches the global `fetch` function. When a direct MPP request returns 402 with a `WWW-Authenticate: Payment` header, the SDK pays the requested amount and retransmits the request with an `Authorization: Payment` credential. Every response after accepted payment includes a `Payment-Receipt` header confirming settlement. ## Step 4: Raw HTTP flow without the SDK If you are not using the `mppx` SDK, you can implement the protocol manually. **Step 1: Request the endpoint** ```bash theme={null} curl -i https://xquik.com/api/v1/x/tweets/1893456789012345678 ``` **Step 2: Receive the 402 challenge** ```text theme={null} HTTP/2 402 WWW-Authenticate: Payment id="abc...", realm="xquik.com", method="tempo", intent="charge", request="eyJhbW91bnQiOi..." ``` The `request` parameter is a base64url-encoded JSON object containing the amount, currency, and recipient address. The `application/problem+json` body also includes an optional `payment_options.guest_wallet.create_checkout` action. It does not replace the MPP header, and the failed request creates no checkout. Ignore it when completing MPP. Use it only after a user explicitly chooses and confirms a $10-$250 USD guest wallet. **Step 3: Pay and retry** After completing the Tempo payment, retry the request with the payment credential: ```bash theme={null} curl -i https://xquik.com/api/v1/x/tweets/1893456789012345678 \ -H "Authorization: Payment eyJjaGFsbGVuZ2UiOnsi..." ``` The `Authorization: Payment` value is a base64url-encoded JSON object containing the original challenge and your payment proof. **Step 4: Receive the response + receipt** ```text theme={null} HTTP/2 200 Payment-Receipt: eyJzdGF0dXMiOiJzdWNjZXNzIiwi... Content-Type: application/json {"tweet": {"id": "1893456789012345678", "text": "..."}} ``` The `Payment-Receipt` header confirms settlement. Check the HTTP status and response body separately because accepted payments also include a receipt on non-2xx responses. ## Next steps * [MPP overview](/mpp/machine-payments-protocol): 7 direct MPP operations, pricing, and protocol details. * [Guest wallets](/guides/guest-wallets): Prepay 33 eligible reads with an accountless API key. * [Get tweet](/api-reference/x/get-tweet): Full endpoint reference for tweet lookups. * [Get user](/api-reference/x/twitter-profile-lookup): Full endpoint reference for user lookups. # Xquik, X & Twitter Trademark & Affiliation Notice Source: https://docs.xquik.com/non-affiliation Learn how Xquik references X, Twitter, tweets, profiles, and related trademarks while operating as an independent third-party service. Includes examples.
For the complete documentation index, see llms.txt.
**Xquik is an independent third-party service.** Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. ## What This Means Xquik builds and operates its own APIs, SDKs, MCP tools, webhooks, dashboard, and documentation. X Corp. does not sponsor, authorize, endorse, or operate Xquik. No partnership, agency, or official integration is implied. References to X, Twitter, tweets, reposts, replies, followers, following, profiles, timelines, communities, lists, direct messages, and other platform features identify the services and workflows that Xquik supports. Those references do not transfer ownership of X Corp. trademarks or products to Xquik. ## Separate Services Xquik controls its subscriptions, credits, API keys, endpoints, SDKs, monitor events, and webhook deliveries. X Corp. controls the X platform, X accounts, official X APIs, developer access, platform rules, and feature availability. Changes made by either service may affect an integration. Use official X Corp. websites when you need X account support, official X API access, X developer terms, trademark policies, or platform status. Use Xquik support when you need help with Xquik API keys, credits, requests, responses, exports, monitors, SDKs, MCP tools, or webhook signatures. ## Your Responsibilities Review the rules that apply to your use case before collecting tweets, profiles, followers, replies, media, or other content. Keep permissions, retention, disclosure, and deletion requirements in mind. Product examples explain Xquik request and response contracts. They are not legal advice or a replacement for applicable platform terms. # OAuth 2.1 for MCP & X API Agent Authorization Source: https://docs.xquik.com/oauth/overview Authorize MCP clients with OAuth 2.1, PKCE, CIMD, and dynamic client registration. Follow token, redirect, discovery, and refresh examples. See examples.
For the complete documentation index, see llms.txt.
Xquik supports [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) Authorization Code with S256 PKCE for remote MCP authentication. Modern clients discover the flow automatically from `https://xquik.com/mcp`, open Xquik login and consent in a browser, then store and refresh Bearer tokens. Prefer OAuth for Claude, ChatGPT, Cursor, VS Code, Windsurf, OpenCode, Gemini CLI, GitHub Copilot CLI, Cline, Qwen Code, and other compatible remote MCP clients. Use the [client compatibility matrix](/mcp/overview#client-compatibility) for Codex, Goose, Roo Code, and Pi. Xquik accepts [API keys](/api-reference/authentication) only when the client documents secure header storage. Affected Codex releases discard the RFC 9207 `iss` value before token exchange. Xquik already returns that value. If Codex reports `Authorization server response missing required issuer: expected https://xquik.com`, use the [Codex API-key fallback](/guides/troubleshooting#codex-oauth-issuer-validation-error) while the [upstream issue](https://github.com/openai/codex/issues/31573) remains open. Xquik supports both MCP client registration paths: * **Client ID Metadata Documents (CIMD)**: Recommended for modern clients. The client's HTTPS metadata URL becomes its `client_id`. No registration request or client secret is required. * **Dynamic Client Registration (DCR)**: Compatibility fallback for clients that do not publish CIMD. The client registers itself once at `/api/oauth/register`. Normal users do not create either configuration manually. Enter `https://xquik.com/mcp` in the client and follow its authentication prompt. ## How it works OAuth 2.1 Authorization Code with PKCE follows this sequence: ```text theme={null} MCP Client Xquik │ │ │ 1. Discover auth metadata │ │────────────────────────────────▶│ │◀────────────────────────────────│ │ 2. Use CIMD or register by DCR │ │ │ │ 3. Generate code_verifier │ │ + code_challenge │ │ │ │ 4. Redirect to /authorize │ │────────────────────────────────▶│ │ │ User logs in │ │ + approves access │ 5. Redirect back with code │ │◀────────────────────────────────│ │ │ │ 6. Exchange code + verifier │ │────────────────────────────────▶│ │◀────────────────────────────────│ │ access_token + refresh_token │ │ │ │ 7. Call MCP with Bearer token │ │────────────────────────────────▶│ ``` ## Discovery Xquik publishes standard OAuth discovery documents so MCP clients can auto-configure endpoints. ### Authorization server metadata ```bash theme={null} curl https://xquik.com/.well-known/oauth-authorization-server ``` ```json Response theme={null} { "issuer": "https://xquik.com", "authorization_endpoint": "https://xquik.com/api/oauth/authorize", "token_endpoint": "https://xquik.com/api/oauth/token", "registration_endpoint": "https://xquik.com/api/oauth/register", "scopes_supported": ["mcp:tools"], "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["none", "client_secret_post"], "revocation_endpoint": "https://xquik.com/api/oauth/revoke", "revocation_endpoint_auth_methods_supported": ["none", "client_secret_post"], "protected_resources": ["https://xquik.com/mcp"], "service_documentation": "https://docs.xquik.com/oauth/overview", "authorization_response_iss_parameter_supported": true, "response_modes_supported": ["query"], "client_id_metadata_document_supported": true, "agent_auth": { "register_uri": "https://xquik.com/api/oauth/register", "claim_uri": "https://xquik.com/api/oauth/authorize", "revocation_uri": "https://xquik.com/api/oauth/revoke", "identity_types_supported": ["anonymous", "oauth_client"], "credential_types_supported": ["oauth_access_token"], "scopes_supported": ["mcp:tools"], "skill": "https://xquik.com/auth.md", "anonymous": { "claim_uri": "https://xquik.com/api/oauth/authorize", "credential_types_supported": ["oauth_access_token"], "scopes_supported": ["mcp:tools"] }, "oauth_client": { "registration_endpoint": "https://xquik.com/api/oauth/register", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "token_endpoint_auth_methods_supported": ["none", "client_secret_post"], "credential_types_supported": ["oauth_access_token"], "scopes_supported": ["mcp:tools"] } } } ``` `agent_auth` is a Xquik discovery extension, not an RFC 8414 or MCP core field. Generic OAuth clients may ignore it. ### Protected resource metadata ```bash theme={null} curl https://xquik.com/.well-known/oauth-protected-resource/mcp ``` ```json Response theme={null} { "resource": "https://xquik.com/mcp", "resource_name": "Xquik MCP Server", "authorization_servers": ["https://xquik.com"], "bearer_methods_supported": ["header"], "resource_documentation": "https://docs.xquik.com/mcp/overview", "scopes_supported": ["mcp:tools"] } ``` The MCP endpoint also returns this metadata URL in its unauthenticated `WWW-Authenticate` challenge: ```text theme={null} Bearer realm="OAuth", resource_metadata="https://xquik.com/.well-known/oauth-protected-resource/mcp", scope="mcp:tools" ``` ## Client registration choices ### Client ID Metadata Document Publish JSON at a stable HTTPS URL with an explicit path. A trailing `/` is sufficient. Use that exact URL as the `client_id`: ```json theme={null} { "client_id": "https://client.example/oauth/client.json", "client_name": "Example MCP Client", "redirect_uris": ["https://client.example/oauth/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none" } ``` The HTTPS `client_id` must include an explicit path. A trailing `/` is enough. Exclude user information, queries, fragments, and dot segments. Xquik requires a direct `200` JSON response. Redirects fail. Keep the document within 5 KiB. Repeat the exact `client_id`. Include `client_name`, `redirect_uris`, and `token_endpoint_auth_method: "none"`. List every allowed redirect URI. ### Dynamic Client Registration Clients without CIMD may register at `POST /api/oauth/register`. DCR supports public clients with `none` and confidential clients with `client_secret_post`. The manual flow below uses a DCR-issued UUID so each step can show a concrete `client_id`. ## Manual implementation Skip this step when the client uses CIMD. For DCR, register once to get a UUID `client_id`. ```bash theme={null} curl -X POST https://xquik.com/api/oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "My MCP Client", "redirect_uris": ["https://myapp.example.com/callback"] }' ``` ```json Response theme={null} { "client_id": "550e8400-e29b-41d4-a716-446655440000", "client_name": "My MCP Client", "redirect_uris": ["https://myapp.example.com/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none" } ``` **Redirect URI requirements:** * Production web callbacks: HTTPS only * Development: HTTP loopback callbacks may use `localhost`, `127.0.0.1`, or `::1` * HTTPS and supported native callbacks require exact matching * HTTP loopback callbacks may change only the ephemeral port. Scheme, host, path, and query must match * Wildcards and subpath matching are not supported **Client types:** * **Public** (`token_endpoint_auth_method: "none"`): Default. No client secret. Used by browser apps and MCP clients. * **Confidential** (`token_endpoint_auth_method: "client_secret_post"`): Returns a `client_secret` in the registration response. Used by server-side apps. If you register a confidential client, the `client_secret` is returned **once** in the registration response. Store it securely. Generate a cryptographically random `code_verifier` and derive the `code_challenge` from it. ```javascript Node.js theme={null} import { randomBytes, createHash } from "node:crypto"; const codeVerifier = randomBytes(32).toString("hex"); const codeChallenge = createHash("sha256") .update(codeVerifier) .digest("base64url"); ``` ```python Python theme={null} import base64 import hashlib import secrets code_verifier = secrets.token_hex(32) code_challenge = base64.urlsafe_b64encode( hashlib.sha256(code_verifier.encode()).digest() ).rstrip(b"=").decode() ``` ```go Go theme={null} package main import ( "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" ) func generatePKCE() (string, string) { b := make([]byte, 32) rand.Read(b) codeVerifier := hex.EncodeToString(b) hash := sha256.Sum256([]byte(codeVerifier)) codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:]) return codeVerifier, codeChallenge } ``` The `code_verifier` must have sufficient entropy. Use at least 32 cryptographically random bytes (64 hex characters). Store the verifier securely on the client. You need it for the token exchange in step 5. Redirect the user to the Xquik authorization endpoint with the required query parameters. ```text theme={null} GET https://xquik.com/api/oauth/authorize ?response_type=code &client_id=550e8400-e29b-41d4-a716-446655440000 &redirect_uri=https://myapp.example.com/callback &code_challenge=a1b2c3d4e5f6... &code_challenge_method=S256 &scope=mcp:tools &state=random_csrf_token &resource=https://xquik.com/mcp ``` **Required parameters:** | Parameter | Value | | ----------------------- | ------------------------------------------------------ | | `response_type` | `code` | | `client_id` | UUID from client registration | | `redirect_uri` | Must match a registered URI exactly | | `code_challenge` | Base64url-encoded SHA256 digest of the `code_verifier` | | `code_challenge_method` | `S256` | | `resource` | `https://xquik.com/mcp` | **Optional parameters:** | Parameter | Default | Description | | --------- | ----------- | -------------------------------- | | `scope` | `mcp:tools` | Only `mcp:tools` is supported | | `state` | | Opaque value for CSRF protection | Xquik defaults `resource` to `https://xquik.com/mcp` for compatibility, but MCP clients must send it in authorization and token requests. The user sees a login page (Google OAuth or email magic link) followed by a consent screen. After approval, Xquik redirects back to your `redirect_uri`. After the user approves, Xquik redirects to your `redirect_uri` with a `code` parameter: ```text theme={null} https://myapp.example.com/callback?code=AUTH_CODE_HERE&state=random_csrf_token&iss=https%3A%2F%2Fxquik.com ``` Verify `state` against the value sent in step 3. Verify `iss` exactly equals the discovered issuer, `https://xquik.com`, before exchanging the code. The authorization code expires in **60 seconds** and is single-use. Exchange the authorization code and your `code_verifier` for an access token and refresh token. ```bash theme={null} curl -X POST https://xquik.com/api/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code\ &code=AUTH_CODE_HERE\ &code_verifier=YOUR_CODE_VERIFIER\ &client_id=550e8400-e29b-41d4-a716-446655440000\ &redirect_uri=https://myapp.example.com/callback\ &resource=https://xquik.com/mcp" ``` ```json Response theme={null} { "access_token": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", "scope": "mcp:tools" } ``` Pass the access token as a Bearer token in the `Authorization` header when connecting to the MCP server. ```bash theme={null} curl https://xquik.com/mcp \ -H "Authorization: Bearer a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" ``` ## Token lifetimes | Token | Lifetime | Notes | | ------------------ | ---------- | ------------------------------------------ | | Access token | 1 hour | Use the refresh token to get a new one | | Refresh token | 30 days | Single-use. Each refresh issues a new pair | | Authorization code | 60 seconds | Single-use. Exchange immediately | ## Refresh tokens Access tokens expire after 1 hour. Use the refresh token to get a new access token without requiring the user to log in again. ```bash theme={null} curl -X POST https://xquik.com/api/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token\ &refresh_token=f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1\ &client_id=550e8400-e29b-41d4-a716-446655440000\ &resource=https://xquik.com/mcp" ``` ```json Response theme={null} { "access_token": "NEW_ACCESS_TOKEN", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "NEW_REFRESH_TOKEN", "scope": "mcp:tools" } ``` Refresh tokens are **single-use**. Each refresh request revokes the old refresh token and returns a new one. Always store the latest refresh token from each response. ## Token revocation Revoke an access or refresh token when a user disconnects or your application no longer needs access. ```bash theme={null} curl -X POST https://xquik.com/api/oauth/revoke \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=ACCESS_OR_REFRESH_TOKEN\ &client_id=550e8400-e29b-41d4-a716-446655440000\ &token_type_hint=access_token" ``` | Parameter | Required | Description | | ----------------- | -------- | --------------------------------------------------------------------------- | | `token` | Yes | The token to revoke | | `client_id` | Yes | The client ID that owns the token | | `token_type_hint` | No | `access_token` or `refresh_token`. Helps the server locate the token faster | Returns `200` with an empty body on success. If the token is already revoked or invalid, the server still returns `200` (per RFC 7009). **Revocation errors:** | Status | Error | When | | ------ | ----------------- | ---------------------------------------------- | | 400 | `invalid_request` | `token` parameter is empty or missing | | 400 | `invalid_request` | `client_id` parameter is empty or missing | | 401 | `invalid_client` | `client_id` does not match a registered client | ## Scopes | Scope | Description | | ----------- | ----------------------------------------------------------------------------------------------- | | `mcp:tools` | Full access to all MCP tools (search tweets, manage monitors, run extractions, run draws, etc.) | Only `mcp:tools` is supported. No partial scopes or scope combinations are available. ## Client registration ### Request ``` POST /api/oauth/register Content-Type: application/json ``` | Field | Type | Required | Description | | ---------------------------- | --------- | -------- | ------------------------------------------------------------------ | | `client_name` | string | No | Display name shown on the consent screen. Defaults to `MCP Client` | | `redirect_uris` | string\[] | Yes | Allowed redirect URIs (1 or more) | | `token_endpoint_auth_method` | string | No | `none` (default) or `client_secret_post` | | `grant_types` | string\[] | No | Defaults to `["authorization_code", "refresh_token"]` | | `response_types` | string\[] | No | Defaults to `["code"]` | ### Response | Field | Type | Description | | ---------------------------- | --------- | ---------------------------------------------------------------- | | `client_id` | string | UUID. Use this in all subsequent OAuth requests | | `client_name` | string | Resolved display name. Defaults to `MCP Client` | | `redirect_uris` | string\[] | Echoed from request | | `grant_types` | string\[] | Resolved grant types | | `response_types` | string\[] | Resolved response types | | `token_endpoint_auth_method` | string | Resolved auth method | | `client_secret` | string | Only present for confidential clients (`client_secret_post`) | | `client_id_issued_at` | number | Unix timestamp when the client ID was issued | | `client_secret_expires_at` | number | Always `0` (non-expiring). Only present for confidential clients | ## Error responses Token, registration, and revocation endpoint errors use the standard OAuth JSON format: ```json theme={null} { "error": "error_code", "error_description": "Human-readable description." } ``` Authorization errors use 2 transports. Xquik returns an HTML error page when it cannot safely trust the client or redirect URI. After validating both, Xquik redirects errors to the registered callback with `error`, `error_description`, optional `state`, and `iss=https://xquik.com` query parameters. ### Authorization errors | Error | When | | --------------------------- | --------------------------------------------------------- | | `unsupported_response_type` | `response_type` is not `code` | | `invalid_request` | Missing or repeated parameters, or missing S256 PKCE data | | `invalid_scope` | Scope is not `mcp:tools` | | `invalid_target` | Resource is not `https://xquik.com/mcp` | | `access_denied` | User denied the authorization request | ### Token errors | Error | When | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | | `invalid_request` | Missing `code`, `code_verifier`, `client_id`, or `refresh_token` | | `invalid_grant` | Code/token is invalid, expired, or already used. Also: `client_id` mismatch, `redirect_uri` mismatch, or PKCE verification failed | | `unsupported_grant_type` | Grant type is not `authorization_code` or `refresh_token` | | `invalid_target` | Resource is not `https://xquik.com/mcp` | ### Registration errors | Error | When | | ------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `invalid_client_metadata` | The JSON body, client name, token authentication method, grant types, or response types are malformed or unsupported | | `invalid_redirect_uri` | `redirect_uris` is missing, malformed, duplicated, too long, unsupported, or exceeds the 10 URI limit | | `temporarily_unavailable` | Registration is rate limited. Honor `Retry-After` before retrying | Read `error_description` for the specific validation failure. Missing or blank `client_name` values default to `MCP Client`. ## Full example A complete Node.js implementation of the OAuth 2.1 flow: ```javascript Node.js theme={null} import { randomBytes, createHash } from "node:crypto"; import http from "node:http"; const CLIENT_ID = "550e8400-e29b-41d4-a716-446655440000"; const REDIRECT_URI = "http://localhost:8080/callback"; // Step 1: Generate PKCE parameters const codeVerifier = randomBytes(32).toString("hex"); const codeChallenge = createHash("sha256") .update(codeVerifier) .digest("base64url"); const state = randomBytes(16).toString("hex"); // Step 2: Build the authorization URL const authUrl = new URL("https://xquik.com/api/oauth/authorize"); authUrl.searchParams.set("response_type", "code"); authUrl.searchParams.set("client_id", CLIENT_ID); authUrl.searchParams.set("redirect_uri", REDIRECT_URI); authUrl.searchParams.set("code_challenge", codeChallenge); authUrl.searchParams.set("code_challenge_method", "S256"); authUrl.searchParams.set("scope", "mcp:tools"); authUrl.searchParams.set("state", state); authUrl.searchParams.set("resource", "https://xquik.com/mcp"); console.log("Open this URL in your browser:"); console.log(authUrl.toString()); // Step 3: Start a local server to receive the callback const server = http.createServer(async (req, res) => { const url = new URL(req.url, "http://localhost:8080"); if (url.pathname !== "/callback") { res.writeHead(404); res.end(); return; } const code = url.searchParams.get("code"); const authorizationError = url.searchParams.get("error"); const returnedIssuer = url.searchParams.get("iss"); const returnedState = url.searchParams.get("state"); // Verify state to prevent CSRF if (returnedState !== state) { res.writeHead(400); res.end("State mismatch"); return; } // Verify the authorization server that issued the response if (returnedIssuer !== "https://xquik.com") { res.writeHead(400); res.end("Issuer mismatch"); return; } if (authorizationError !== null || code === null) { res.writeHead(400); res.end("Authorization failed"); return; } // Step 4: Exchange the authorization code for tokens const tokenResponse = await fetch("https://xquik.com/api/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code, code_verifier: codeVerifier, client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, resource: "https://xquik.com/mcp", }), }); if (!tokenResponse.ok) { res.writeHead(502); res.end("Token exchange failed"); return; } const tokens = await tokenResponse.json(); // Store tokens in a secret store. Never print them or include them in logs. void tokens; res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Authorization complete. You can close this tab."); server.close(); }); server.listen(8080); ``` ## Where to go next Connect AI agents to Xquik via MCP. All MCP tools with input/output schemas. API key authentication for REST API and MCP. Get your API key and make your first request. # X/Twitter SDKs & CLI for Tweet Search & Exports Source: https://docs.xquik.com/sdks Install Xquik SDKs and CLI tools for tweet search exports, media uploads, DMs, webhooks, MCP handoff, and X API automation. Includes install and code examples.
For the complete documentation index, see llms.txt.
Official Xquik SDKs wrap the REST API with generated request types, response models, retries, pagination helpers, and consistent authentication. Use this page to choose the right SDK, CLI, or MCP handoff for tweet search exports, follower export jobs, media tweets, DM attachments, monitor webhooks, and agent workflows. For SDK extraction jobs, treat `202 Accepted` as a queued run receipt. Poll `GET /extractions/{id}` before exporting rows. Credits are reserved after the job starts, so the run can lower `resultsLimit` to the affordable count or fail with `insufficient_credits`. Use typed REST calls for Node.js, Bun, Next.js route handlers, tweet search exports, media tweets, and DMs. Build sync or async jobs for tweet search, JSON Lines, CSV, XLSX, monitors, and webhook automation. Add tweet search exports, write actions, and monitor workers to Go services with context-aware calls. Use Maven or Gradle to add the Java SDK to Spring, worker, and JVM backend applications. Use idiomatic Kotlin types, nullable values, sequences, and suspend-friendly client surfaces. Install from NuGet. Use REST for support attachment downloads. Use the Ruby gem with Yard, RBS, RBI, retries, and connection pooling. Add the Composer package to PHP 8.1+ applications with named parameters and typed exceptions. Run terminal workflows for tweet search exports, JSON output, CSV/XLSX handoff, and scripted API calls. Install from the Terraform Registry and manage Xquik resources as infrastructure. ## Choose an SDK Install with `npm install x-twitter-scraper`; source: [Xquik-dev/x-twitter-scraper-typescript](https://github.com/Xquik-dev/x-twitter-scraper-typescript). Open the [TypeScript SDK guide](/sdks/typescript). Install with `pip install x_twitter_scraper`; source: [Xquik-dev/x-twitter-scraper-python](https://github.com/Xquik-dev/x-twitter-scraper-python). Open the [Python SDK guide](/sdks/python). Install with `go get github.com/Xquik-dev/x-twitter-scraper-go`; source: [Xquik-dev/x-twitter-scraper-go](https://github.com/Xquik-dev/x-twitter-scraper-go). Open the [Go SDK guide](/sdks/go). Maven Central publication is pending. Build from source at [Xquik-dev/x-twitter-scraper-java](https://github.com/Xquik-dev/x-twitter-scraper-java). Open the [Java SDK guide](/sdks/java). Maven Central publication is pending. Build from source at [Xquik-dev/x-twitter-scraper-kotlin](https://github.com/Xquik-dev/x-twitter-scraper-kotlin). Open the [Kotlin SDK guide](/sdks/kotlin). Install with `dotnet add package XTwitterScraper`; use REST for `GET /support/attachments/{id}`. Source: [Xquik-dev/x-twitter-scraper-csharp](https://github.com/Xquik-dev/x-twitter-scraper-csharp). Open the [C# SDK guide](/sdks/csharp-x-api-sdk). Install with `gem install x-twitter-scraper`; source: [Xquik-dev/x-twitter-scraper-ruby](https://github.com/Xquik-dev/x-twitter-scraper-ruby). Open the [Ruby SDK guide](/sdks/ruby). Install with `composer require xquik/x-twitter-scraper`; source: [Xquik-dev/x-twitter-scraper-php](https://github.com/Xquik-dev/x-twitter-scraper-php). Open the [PHP SDK guide](/sdks/php). Install with `go install github.com/Xquik-dev/x-twitter-scraper-cli/cmd/x-twitter-scraper@latest`; source: [Xquik-dev/x-twitter-scraper-cli](https://github.com/Xquik-dev/x-twitter-scraper-cli). Open the [CLI guide](/sdks/cli). Install `Xquik-dev/x-twitter-scraper` from the Terraform Registry. Open the [Terraform guide](/sdks/terraform). ## Choose by Job Start with [TypeScript](/sdks/typescript), [Python](/sdks/python), [Go SDK](/sdks/go), or [CLI](/sdks/cli) plus [Search Tweets](/api-reference/x/search-tweets). Hand off `PaginatedTweets`, `tweets`, `has_next_page`, `next_cursor`, and `xquik-tweet-search.jsonl` for JSON Lines, CSV, or XLSX. Cost: 1 credit per tweet returned. Start with [Follower Export CRM Workflow](/guides/follower-export-crm), [TypeScript](/sdks/typescript), [Python](/sdks/python), [Go SDK](/sdks/go), or [CLI](/sdks/cli). Run `follower_explorer` with `targetUsername` and optional `resultsLimit`, then export CSV, JSON, or XLSX from [Export Extraction](/api-reference/extractions/export). Store the `202 Accepted` receipt `id` and `toolType`, then poll `GET /extractions/{id}` for `job.status`, `results`, `hasMore`, and `nextCursor`. Cost: 1 credit per follower extracted or returned. Start with [TypeScript](/sdks/typescript), [Go SDK](/sdks/go), or [CLI](/sdks/cli) plus [Create Tweet](/api-reference/x-write/create-tweet). Pass public media URLs in `media`: up to 4 image URLs or exactly 1 MP4 video URL up to 100 MB. Store `tweetId`, `reply_to_tweet_id`, `chargedCredits`, or `writeActionId`. Cost: 30 credits text-only, plus 2 credits per started MB across attached media. Call [Upload Media](/api-reference/x-write/upload-media), then [Send Direct Message](/api-reference/x-write/send-dm). Use the returned `mediaId` as the one-item `media_ids` value; store `mediaUrl` and the returned `messageId`. Cost: 10 credits per media upload plus 10 credits per DM send. Start with [Create Monitor](/api-reference/monitors/create), [Webhooks](/webhooks/overview), or [Terraform](/sdks/terraform). Store monitor IDs, webhook IDs, event types, and signed delivery metadata. Active active monitors cost 21 credits per monitor-hour. Use the [MCP Server](/mcp/overview) for tool calls, or SDKs for direct code. Return JSON objects with the endpoint path, request parameters, result fields, pagination cursor, and endpoint cost. ## Authentication All SDKs accept the same API credentials as the REST API: ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` OAuth 2.1 bearer tokens are supported where the generated SDK exposes `X_TWITTER_SCRAPER_BEARER_TOKEN`. ## Common Workflows * Search tweets or scrape tweets to JSON Lines, CSV, or XLSX through [Search Tweets](/api-reference/x/search-tweets). Keep `xquik-tweet-search.jsonl` or the language-specific export file as the downstream handoff. * Inspect threads through [Get Tweet](/api-reference/x/get-tweet) and [Tweet Thread](/api-reference/x/tweet-thread). * Look up profiles and relationships through [Get User](/api-reference/x/twitter-profile-lookup), [Followers](/api-reference/x/followers), and [Check Follower](/api-reference/x/check-follower). * Post media tweets or replies with up to 4 public image URLs or exactly 1 public MP4 video URL up to 100 MB through [Create Tweet](/api-reference/x-write/create-tweet). Text-only tweet or reply writes cost 30 credits, and attached media adds 2 credits per started MB across all files. Upload media through [Upload Media](/api-reference/x-write/upload-media) when [Send Direct Message](/api-reference/x-write/send-dm) needs one uploaded media ID in `media_ids`. * Create account monitors with [Create Monitor](/api-reference/monitors/create), then receive events through [Webhooks](/webhooks/overview). * Run exports and giveaway workflows with [Extractions](/api-reference/extractions/create) and [Draws](/api-reference/draws/create). * Connect agents through the [MCP Server](/mcp/overview) when you need tool use instead of direct SDK calls. ## Error Handling & Pagination Generated SDKs map HTTP errors into language-native exception or error types. Start with the [error handling guide](/guides/error-handling) for status code semantics and the per-language page for idiomatic handling. Paginated endpoints return a page object with a next-page marker such as `has_next_page`, `hasNextPage`, or `HasNextPage` depending on language casing. Use the SDK's generated pagination helpers when available, or pass the cursor fields documented on each endpoint. # X API CLI for Tweet Search, Exports & Automation Source: https://docs.xquik.com/sdks/cli Use the Xquik CLI to search tweets, export JSON Lines, CSV, or XLSX, post media tweets, send DMs, monitor tweets, and script X API workflows from a terminal.
For the complete documentation index, see llms.txt.
Use the CLI when a shell script, cron job, CI workflow, or operations runbook needs X API data without an application wrapper. It can search tweets, scrape tweets to JSON Lines, CSV, or XLSX, export follower data, upload media, post media tweets, post tweet replies, send direct messages, monitor tweets, and hand results to `jq`, Python, a warehouse loader, or a queue worker. ## Install ```bash theme={null} go install github.com/Xquik-dev/x-twitter-scraper-cli/cmd/x-twitter-scraper@latest ``` Make sure your Go bin directory is on `PATH`: ```bash theme={null} export PATH="$PATH:$(go env GOPATH)/bin" ``` ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` You can also pass `--api-key` per command or use `X_TWITTER_SCRAPER_BEARER_TOKEN` for OAuth 2.1 access tokens. ## Basic Example ```bash theme={null} x-twitter-scraper x:tweets search \ --q from:elonmusk \ --limit 10 \ --format json ``` Use `--help` at any level: ```bash theme={null} x-twitter-scraper x:tweets search --help ``` ## Workflow: Search Tweets to JSON Lines, CSV, or XLSX Use this workflow when an analyst, data engineer, or growth operator needs tweets from an X search query in a JSON Lines handoff, analyst CSV file, XLSX workbook, warehouse load, CRM enrichment job, or queue. The CLI command below calls `GET /x/tweets/search`. It maps the same REST API query parameters to flags: `--q`, `--limit`, `--cursor`, `--since-time`, `--until-time`, and `--query-type`. ```bash theme={null} query="from:username webhook OR SDK" cursor="" page_index=0 headers='["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"]' : > xquik-tweet-search.jsonl jq -nr "$headers | @csv" > xquik-tweet-search.csv while :; do page_cursor="$cursor" if [ -n "$cursor" ]; then x-twitter-scraper x:tweets search \ --q "$query" \ --query-type Latest \ --cursor "$cursor" \ --limit 100 \ --format json \ --format-error json > search-page.json else x-twitter-scraper x:tweets search \ --q "$query" \ --query-type Latest \ --limit 100 \ --format json \ --format-error json > search-page.json fi has_next_page="$(jq -r '.has_next_page // false' search-page.json)" next_cursor="$(jq -r 'if .has_next_page then (.next_cursor // "") else "" end' search-page.json)" jq -c \ --arg source "xquik.cli.search" \ --arg query "$query" \ --arg page_cursor "$page_cursor" \ --arg next_cursor "$next_cursor" \ --argjson page_index "$page_index" \ --argjson has_next_page "$has_next_page" \ '.tweets[] | { source: $source, query: $query, tweet_id: .id, text: .text, author_id: (.author.id // ""), author_username: (.author.username // ""), author_name: (.author.name // ""), created_at: (.createdAt // ""), like_count: (.likeCount // 0), reply_count: (.replyCount // 0), retweet_count: (.retweetCount // 0), quote_count: (.quoteCount // 0), view_count: (.viewCount // 0), bookmark_count: (.bookmarkCount // 0), is_note_tweet: (.isNoteTweet // false), page_index: $page_index, page_cursor: $page_cursor, next_cursor: $next_cursor, has_next_page: $has_next_page }' search-page.json >> xquik-tweet-search.jsonl jq -r \ --arg source "xquik.cli.search" \ --arg query "$query" \ --arg page_cursor "$page_cursor" \ --arg next_cursor "$next_cursor" \ --argjson page_index "$page_index" \ --argjson has_next_page "$has_next_page" \ '.tweets[] | [ $source, $query, .id, .text, (.author.id // ""), (.author.username // ""), (.author.name // ""), (.createdAt // ""), (.likeCount // 0), (.replyCount // 0), (.retweetCount // 0), (.quoteCount // 0), (.viewCount // 0), (.bookmarkCount // 0), (.isNoteTweet // false), $page_index, $page_cursor, $next_cursor, $has_next_page ] | @csv' search-page.json >> xquik-tweet-search.csv [ "$has_next_page" = "true" ] || break [ -n "$next_cursor" ] || break cursor="$next_cursor" page_index=$((page_index + 1)) done ``` The response includes `.tweets[]`, `.has_next_page`, and `.next_cursor`. Each tweet can include `id`, `text`, `createdAt`, engagement counts, `bookmarkCount`, `isNoteTweet`, `author`, `media`, `quoted_tweet`, and `retweeted_tweet`, depending on what X returns for the result. Project `.tweets[]` into `xquik-tweet-search.jsonl` and `xquik-tweet-search.csv` rows with `tweet_id`, `author_username`, engagement counts, `page_index`, `page_cursor`, `next_cursor`, and `has_next_page` so scripts can resume safely or load the same records into XLSX, CRM, warehouse, or agent workflows. ```bash theme={null} python3 - <<'PY' import csv import json rows = [json.loads(line) for line in open("xquik-tweet-search.jsonl", encoding="utf-8")] fieldnames = [ "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", ] with open("xquik-tweet-search-from-jsonl.csv", "w", newline="", encoding="utf-8") as handle: writer = csv.DictWriter(handle, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) PY ``` Save the cursor with the job checkpoint before requesting the next page: ```bash theme={null} tail -n 1 xquik-tweet-search.jsonl | jq '{query, page_index, next_cursor, has_next_page}' ``` Use `--limit` as a 1 to 200 upper bound for a bounded pull. When `.has_next_page` is true, keep the same `--q`, filters, `--query-type`, and `--limit`; only `--cursor` changes. 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`. For bounded pulls that return fewer tweets than the requested `--limit`, pass `.next_cursor` back as `--cursor` with the same query, filters, `--query-type`, and `--limit`. Use `--format-error json` so batch jobs can branch on `status` and `code`, and retry `429` or temporary `5xx` responses with backoff. For XLSX handoff, keep `xquik-tweet-search.jsonl` as the source of truth and convert the same projected rows in your pipeline when account managers need a workbook. ## Workflow: Follower Export to CSV, JSON, or XLSX Use this workflow when sales, support, research, or marketing teams need an owned follower list in a CRM import, warehouse load, analyst CSV file, XLSX workbook, or resumable JSON handoff. `follower_explorer` requires `targetUsername`. The CLI flag is `--target-username`. ```bash theme={null} x-twitter-scraper extractions estimate-cost \ --tool-type follower_explorer \ --target-username username \ --format json > follower-estimate.json ``` The estimate calls `POST /extractions/estimate` and returns `allowed`, `estimatedResults`, `creditsRequired`, `creditsAvailable`, and `source`. For follower exports, `source` is usually `followers`. ```bash theme={null} jq -e '.allowed == true' follower-estimate.json >/dev/null x-twitter-scraper extractions run \ --tool-type follower_explorer \ --target-username username \ --format json > follower-run.json jq -e '.status == "running" and .toolType == "follower_explorer"' follower-run.json >/dev/null job_id="$(jq -r '.id' follower-run.json)" ``` The run command calls `POST /extractions` and returns the queued `202 Accepted` receipt: `id`, `toolType`, and `status: "running"`. Persist `follower-run.json`, `job_id`, the source username, and the estimate before polling. Credit reservation happens after the job starts. If available credits changed since the estimate, the run can fetch only the affordable count before export or mark the job `failed` with `insufficient_credits`. ```bash theme={null} while :; do x-twitter-scraper extractions retrieve \ --id "$job_id" \ --limit 1000 \ --format json > followers-page.json status="$(jq -r '.job.status' followers-page.json)" [ "$status" = "completed" ] && break [ "$status" = "failed" ] && exit 1 sleep 10 done ``` The retrieve command returns `.job`, `.results`, `.hasMore`, and `.nextCursor`. When `.hasMore` is true, pass `.nextCursor` back as `--cursor` to fetch the next saved page. ```bash theme={null} : > xquik-followers.jsonl cursor="" while :; do if [ -n "$cursor" ]; then x-twitter-scraper extractions retrieve \ --id "$job_id" \ --cursor "$cursor" \ --limit 1000 \ --format json > followers-page.json else x-twitter-scraper extractions retrieve \ --id "$job_id" \ --limit 1000 \ --format json > followers-page.json fi jq -c '.results[]' followers-page.json >> xquik-followers.jsonl has_more="$(jq -r '.hasMore // false' followers-page.json)" next_cursor="$( jq -r 'if .hasMore then (.nextCursor // "") else "" end' followers-page.json )" [ "$has_more" = "true" ] || break [ -n "$next_cursor" ] || break cursor="$next_cursor" done ``` ```bash theme={null} x-twitter-scraper extractions export-results \ --id "$job_id" \ --format csv \ --output xquik-followers.csv ``` Use `--format json --output xquik-followers.json` for app ingestion or `--format xlsx --output xquik-followers.xlsx` for XLSX handoff. Map `User ID` or result `xUserId` as the CRM unique key. Keep `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. ## Workflow: Tweet Replies to CSV, JSON, or XLSX Use this workflow when you need every reply under one Tweet. Export CSV, JSON, or XLSX. Write a local JSON Lines file from paginated JSON rows. `reply_extractor` requires `targetTweetId`. The CLI flag is `--target-tweet-id`. ```bash theme={null} x-twitter-scraper extractions estimate-cost \ --tool-type reply_extractor \ --target-tweet-id 1893704267862470862 \ --format json > reply-estimate.json ``` The estimate calls `POST /extractions/estimate` and returns `allowed`, `estimatedResults`, `creditsRequired`, `creditsAvailable`, and `source`. For replies, `source` is usually `replyCount`. ```bash theme={null} jq -e '.allowed == true' reply-estimate.json >/dev/null job_id="$(x-twitter-scraper extractions run \ --tool-type reply_extractor \ --target-tweet-id 1893704267862470862 \ --transform id \ --raw-output)" ``` The run command calls `POST /extractions`. Persist `job_id` before polling so a shell restart, queue retry, or CI rerun can resume the same extraction. Reuse the follower export polling loop above with reply filenames: ```bash theme={null} x-twitter-scraper extractions retrieve \ --id "$job_id" \ --limit 100 \ --format json > replies-page.json next_cursor="$(jq -r '.nextCursor // empty' replies-page.json)" x-twitter-scraper extractions retrieve \ --id "$job_id" \ --cursor "$next_cursor" \ --limit 100 \ --format json > replies-next-page.json ``` The retrieve command calls `GET /extractions/{id}` and returns `.job`, `.results`, `.hasMore`, and `.nextCursor`. Append `.results[]` to your queue, CRM import, or warehouse load. While `.hasMore` is true, pass `.nextCursor` back as `--cursor "$next_cursor"`. ```bash theme={null} x-twitter-scraper extractions export-results \ --id "$job_id" \ --format csv \ --output xquik-tweet-replies.csv ``` The export command calls `GET /extractions/{id}/export` and writes bytes to `--output`. Use `--format csv`, `--format json` with `--output xquik-tweet-replies.json`, or `--format xlsx` with `--output xquik-tweet-replies.xlsx`. Cost: 1 credit per reply extracted or returned. Exports are free after the extraction job exists. ## Workflow: Post Media Tweets, Replies, and DM Attachments Use this workflow when an operator, support queue, or agent needs to publish a media-backed tweet, reply with media, or send one media attachment in a DM from a connected X account. Tweet and reply media posts use public media URLs directly on `x:tweets create`. Pass repeated `--media` flags for up to 4 image URLs, or pass exactly 1 MP4 video URL up to 100 MB. Do not mix video with other media. Do not upload first when you already have public media URLs. ```bash theme={null} x-twitter-scraper x:tweets create \ --account @username \ --text "New demo video is live." \ --media https://example.com/product-demo.mp4 \ --format json \ --format-error json > posted-tweet.json ``` To reply with media, add the parent tweet ID. The `--reply-to-tweet-id` flag maps to `reply_to_tweet_id`, and `--media` maps to the public URL array on `POST /x/tweets`. ```bash theme={null} x-twitter-scraper x:tweets create \ --account @username \ --text "Here is the requested screenshot." \ --reply-to-tweet-id 1893704267862470862 \ --media https://example.com/export-preview.png \ --format json \ --format-error json > posted-reply.json ``` Send a unique `Idempotency-Key`. Store the durable action's `id`, `request.hash`, `billing`, `result`, and `statusUrl`. Poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. Keep one JSON Lines handoff shape for every lifecycle state: ```bash theme={null} write_handoff() { input_file="$1" jq -c ' { status, terminal, safe_to_retry: .safeToRetry, write_action_id: .id, request_hash: .request.hash, tweet_id: (.result.id // .tweetId // null), charged: .billing.charged, charged_credits: .billing.chargedCredits, poll: (if .terminal then null else .statusUrl end) } ' "$input_file" } write_handoff posted-tweet.json > tweet-handoff.jsonl write_handoff posted-reply.json > reply-handoff.jsonl ``` Each create-tweet call costs 30 credits. Use `x:media upload` when you need an uploaded media ID for a DM attachment. The CLI upload command accepts a local file through `--file`; `--transform mediaId` extracts the upload response ID for scripts. ```bash theme={null} media_id="$(x-twitter-scraper x:media upload \ --account @username \ --file ./handoff.png \ --transform mediaId \ --raw-output)" x-twitter-scraper x:dm send \ --account @username \ --user-id 44196397 \ --text "Here is the asset." \ --media-id "$media_id" \ --format json > sent-dm.json jq -c --arg media_id "$media_id" '{ status: "sent", message_id: .messageId, media_id: $media_id }' sent-dm.json > dm-handoff.jsonl ``` The `--media-id` flag maps to the REST `media_ids` body field. DMs accept exactly 1 uploaded media ID. Store `messageId` from the DM response on the support ticket, CRM record, queue job, or agent memory. Keep DM body text in private systems. Shared logs, public artifacts, queue status, and agent handoffs should store `message_id`, optional `media_id`, recipient/account identifiers from your job context, and send status instead of full DM bodies. Uploading media costs 10 credits, and sending the DM costs 10 credits. Do not pass `--reply-to-message-id` to `x:dm send`; the REST endpoint rejects `reply_to_message_id`. Start a new DM with `--user-id`, `--account`, `--text`, and optional one-item `--media-id` instead. Do not pass uploaded `mediaId` values to `x:tweets create`; that command uses `--media` with public media URLs. ## Useful Commands Run `x-twitter-scraper x:tweets search` to return tweet objects, author objects, metrics, media, `has_next_page`, and `next_cursor`. Run `x-twitter-scraper x:tweets retrieve` to return the full tweet text, author, metrics, media, quoted tweet, and retweeted tweet. Run `x-twitter-scraper x:tweets get-replies` to return reply tweets plus cursor fields. Run `x-twitter-scraper x:users retrieve-followers` to return user profiles, follower counts, and cursor fields. Run `x-twitter-scraper x:tweets create` to return `tweetId`, `success`, `charged`, and `chargedCredits`, or `writeActionId` when confirmation is pending. Run `x-twitter-scraper x:media upload` to return `mediaId` for one-item DM `media_ids` and `mediaUrl` for tweet `media` URL handoff. Run `x-twitter-scraper x:dm send` to return `messageId` and `success`. Use `--format json` for scripts, `--format yaml` for readable operations output, and `--transform` with GJSON syntax when you only need one field from the response. ## Error Handling The CLI writes API errors to stderr and exits non-zero for failed requests. Use `--format-error json` when scripts need machine-readable errors. ```bash theme={null} x-twitter-scraper x:users retrieve-search \ --q not-a-real-user \ --format json \ --format-error json ``` Retryable API responses follow the same semantics as the REST API. See [error handling](/guides/error-handling) and [rate limits](/guides/rate-limits). ## Pagination Commands that call paginated endpoints include pagination fields in their JSON output. Keep the response body when your script needs to request the next page with endpoint cursor parameters. ## Webhooks & References * [Search Tweets](/api-reference/x/search-tweets) * [Get User](/api-reference/x/twitter-profile-lookup) * [Create Tweet](/api-reference/x-write/create-tweet) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [REST API Overview](/api-reference/overview) * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-cli) # C# SDK for Tweet Search, Exports & X Automation Source: https://docs.xquik.com/sdks/csharp-x-api-sdk Use the Xquik C# SDK to search tweets, export tweet replies to JSON Lines, CSV, or XLSX, post media tweets, upload media, send DMs, and run .NET X API workers.
For the complete documentation index, see llms.txt.
Use the C# SDK for typed .NET Standard 2.0+ access to Xquik from services, workers, console tools, and ASP.NET backends. It is useful when a .NET job needs to search tweets, scrape tweets or replies to JSON Lines, CSV, or XLSX, export followers, monitor tweets, post media tweets, upload media, send direct messages, or hand X API data to queues, CRMs, and reporting systems. | C# worker task | SDK call | Durable checkpoint | | ---------------------------------- | ---------------------------------------- | ------------------------------------------------ | | Search tweets | `client.X.Tweets.Search` | Persist `page.NextCursor` after every page. | | Estimate follower or reply exports | `client.Extractions.EstimateCost` | Store the estimate and `Allowed` decision. | | Start an extraction | `client.Extractions.Run` | Save `job.ID` immediately. | | Retrieve saved rows | `client.Extractions.Retrieve` | Keep `NextCursor` while `HasMore` is true. | | Export CSV, JSON, or XLSX | `client.Extractions.ExportResults` | Record the job ID, format, and file destination. | | Create a tweet | `client.X.Tweets.WithRawResponse.Create` | Store the write action ID and terminal state. | ## Install The current NuGet package excludes `GET /support/attachments/{id}`. Use REST for that binary download. ```bash theme={null} dotnet add package XTwitterScraper ``` ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` `new XTwitterScraperClient()` reads `X_TWITTER_SCRAPER_API_KEY`, `X_TWITTER_SCRAPER_BEARER_TOKEN`, and `X_TWITTER_SCRAPER_BASE_URL`. ## Basic Example Search tweets and write durable JSON Lines handoff rows: ```csharp theme={null} using System.Text.Json; using XTwitterScraper; using XTwitterScraper.Models; using XTwitterScraper.Models.X.Tweets; XTwitterScraperClient client = new(); TweetSearchParams parameters = new() { Q = "from:username webhook OR SDK", Limit = 10, }; PaginatedTweets page = await client.X.Tweets.Search(parameters); foreach (SearchTweet tweet in page.Tweets) { var row = new { tweet_id = tweet.ID, text = tweet.Text, author_username = tweet.Author?.Username, created_at = tweet.CreatedAt, }; await Console.Out.WriteLineAsync(JsonSerializer.Serialize(row)); } ``` ## Workflow: Search Tweets to JSON Lines, CSV, or XLSX Use this workflow when a .NET worker, console job, or ASP.NET background service needs tweet search results in a durable handoff format for a queue, data lake, analyst CSV export, XLSX workbook, or CRM enrichment step. `client.X.Tweets.Search` calls `GET /x/tweets/search`. Build a `TweetSearchParams` object with the same query parameters the REST API accepts: `Q`, `Limit`, `Cursor`, `SinceTime`, `UntilTime`, and `QueryType`. ```csharp theme={null} using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using XTwitterScraper; using XTwitterScraper.Models; using XTwitterScraper.Models.X.Tweets; XTwitterScraperClient client = new(); string query = "from:username webhook OR SDK"; string? cursor = null; int pageIndex = 0; string[] headers = new[] { "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", }; using StreamWriter jsonlWriter = File.CreateText("xquik-tweet-search.jsonl"); using StreamWriter csvWriter = File.CreateText("xquik-tweet-search.csv"); await WriteCsvRow(csvWriter, headers); do { string? pageCursor = cursor; PaginatedTweets page = await client.X.Tweets.Search( new TweetSearchParams { Q = query, Cursor = cursor, QueryType = QueryType.Latest, } ); foreach (SearchTweet tweet in page.Tweets) { Dictionary row = new() { ["source"] = "xquik.csharp.search", ["query"] = query, ["tweet_id"] = tweet.ID, ["text"] = tweet.Text, ["author_id"] = tweet.Author?.ID, ["author_username"] = tweet.Author?.Username, ["author_name"] = tweet.Author?.Name, ["created_at"] = tweet.CreatedAt, ["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, ["page_index"] = pageIndex, ["page_cursor"] = pageCursor, ["next_cursor"] = page.HasNextPage ? page.NextCursor : null, ["has_next_page"] = page.HasNextPage, }; await jsonlWriter.WriteLineAsync(JsonSerializer.Serialize(row)); await WriteCsvRow(csvWriter, headers.Select(header => row[header])); } cursor = page.HasNextPage ? page.NextCursor : null; pageIndex++; } while (!string.IsNullOrEmpty(cursor)); static async Task WriteCsvRow(StreamWriter writer, IEnumerable values) { static string Escape(object? value) { string cell = value?.ToString() ?? ""; return "\"" + cell.Replace("\"", "\"\"") + "\""; } await writer.WriteLineAsync(string.Join(",", values.Select(Escape))); } ``` ### Request Mapping C# property `Q` maps to REST `q`. Use it for an X search query such as `from:username`, a keyword, hashtag, or boolean operator query. C# property `Limit` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.HasNextPage` is true, keep the same `Q`, filters, `QueryType`, and `Limit` when you continue with `page.NextCursor`. C# property `Cursor` maps to REST `cursor`. Pass the opaque cursor from `page.NextCursor` to request the next page. C# property `SinceTime` maps to REST `sinceTime`. Use it as the ISO 8601 lower bound for tweet creation time. C# property `UntilTime` maps to REST `untilTime`. Use it as the ISO 8601 upper bound for tweet creation time. C# property `QueryType` maps to REST `queryType`. Use `QueryType.Latest` for chronological search or `QueryType.Top` for engagement-ranked search. ### Returned Data & Handoff `client.X.Tweets.Search` returns `PaginatedTweets`. Use `page.Tweets` for the tweet array, `page.HasNextPage` to decide whether another page exists, and `page.NextCursor` as the checkpoint for the next request. For bounded pulls that return fewer tweets than `Limit`, pass `page.NextCursor` back as `Cursor` with the same query, filters, `QueryType`, and `Limit`. Each `SearchTweet` includes typed properties 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 retries connection errors, 408, 409, 429, and 5xx responses 2 times by default. For explicit `Limit` pulls, resume with the same query, filters, `QueryType`, and `Limit`; only `Cursor` changes. ## Workflow: Follower Export to CSV, JSON, or XLSX Use this workflow when a .NET worker, console job, or ASP.NET background service needs an owned follower list for a CRM import, warehouse load, analyst CSV file, XLSX workbook, or resumable JSON handoff. `client.Extractions.EstimateCost` and `client.Extractions.Run` map to the extraction workflow. For follower exports, use `ExtractionEstimateCostParamsToolType.FollowerExplorer` and `ExtractionRunParamsToolType.FollowerExplorer`; `follower_explorer` requires `TargetUsername`. `client.Extractions.Retrieve` returns `Results`, `HasMore`, and `NextCursor` for pagination. `client.Extractions.ExportResults` returns an `HttpResponse`; read CSV and JSON as strings, and copy XLSX from the response stream. `client.Extractions.Run` returns the queued `202 Accepted` receipt from `POST /extractions`: REST `id`, `toolType`, and `status: "running"` as C# `job.ID`, `job.ToolType`, and `job.Status`. Store `job.ID` immediately, then poll `client.Extractions.Retrieve` before reading pages or calling `client.Extractions.ExportResults`. Credit reservation happens after the job starts. If available credits changed since `EstimateCost`, the run can fetch only the affordable count before export or mark the job `failed` with `insufficient_credits`. ```csharp theme={null} using System; using System.IO; using System.Text.Json; using System.Threading.Tasks; using XTwitterScraper; using XTwitterScraper.Core; using XTwitterScraper.Models.Extractions; XTwitterScraperClient client = new(); string targetUsername = "username"; ExtractionEstimateCostResponse estimate = await client.Extractions.EstimateCost( new ExtractionEstimateCostParams { ToolType = ExtractionEstimateCostParamsToolType.FollowerExplorer, TargetUsername = targetUsername, } ); if (!estimate.Allowed) { throw new InvalidOperationException("Insufficient credits for follower export."); } ExtractionRunResponse job = await client.Extractions.Run( new ExtractionRunParams { ToolType = ExtractionRunParamsToolType.FollowerExplorer, TargetUsername = targetUsername, } ); while (true) { ExtractionRetrieveResponse statusPage = await client.Extractions.Retrieve( job.ID, new ExtractionRetrieveParams { Limit = 1 } ); string? status = statusPage.Job.TryGetValue("status", out JsonElement statusValue) ? statusValue.GetString() : null; if (status == "completed") { break; } if (status == "failed") { throw new InvalidOperationException("Follower export failed."); } await Task.Delay(TimeSpan.FromSeconds(10)); } string? cursor = null; using StreamWriter writer = File.CreateText("xquik-followers.jsonl"); do { ExtractionRetrieveResponse page = await client.Extractions.Retrieve( job.ID, new ExtractionRetrieveParams { Cursor = cursor, Limit = 1000 } ); foreach (var follower in page.Results) { await writer.WriteLineAsync(JsonSerializer.Serialize(follower)); } cursor = page.HasMore ? page.NextCursor : null; } while (!string.IsNullOrEmpty(cursor)); using HttpResponse csvResponse = await client.Extractions.ExportResults( job.ID, new ExtractionExportResultsParams { Format = Format.Csv } ); await File.WriteAllTextAsync("xquik-followers.csv", await csvResponse.ReadAsString()); using HttpResponse jsonResponse = await client.Extractions.ExportResults( job.ID, new ExtractionExportResultsParams { Format = Format.Json } ); await File.WriteAllTextAsync("xquik-followers.json", await jsonResponse.ReadAsString()); using HttpResponse xlsxResponse = await client.Extractions.ExportResults( job.ID, new ExtractionExportResultsParams { Format = Format.Xlsx } ); using Stream xlsxStream = await xlsxResponse.ReadAsStream(); using FileStream xlsxFile = File.Create("xquik-followers.xlsx"); await xlsxStream.CopyToAsync(xlsxFile); ``` Cost: 1 credit per follower extracted or returned. Persist `job.ID`, `targetUsername`, `estimate.EstimatedResults`, and `estimate.Source` before polling so a queue retry, Windows service restart, or worker restart can resume the same follower export. Keep `page.NextCursor` as the checkpoint when you stream followers to JSON Lines, and 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. Exports are free after the extraction job exists. ## Workflow: Tweet Replies to CSV, JSON, or XLSX Use this workflow when a .NET worker needs every reply on a public tweet in a durable file for moderation review, customer support triage, analyst export, or a warehouse loader. `client.Extractions.EstimateCost` and `client.Extractions.Run` map to the extraction workflow. For tweet replies, use `ExtractionEstimateCostParamsToolType.ReplyExtractor` and `ExtractionRunParamsToolType.ReplyExtractor`; `reply_extractor` requires `TargetTweetID`. `client.Extractions.Retrieve` returns `Results`, `HasMore`, and `NextCursor` for pagination. `client.Extractions.ExportResults` returns an `HttpResponse`; read CSV and JSON as strings, and copy XLSX from the response stream. Reuse the follower export loop above. Change only the required target, tool type, and output names: ```csharp theme={null} string targetTweetID = "1893704267862470862"; ExtractionEstimateCostResponse estimate = await client.Extractions.EstimateCost( new ExtractionEstimateCostParams { ToolType = ExtractionEstimateCostParamsToolType.ReplyExtractor, TargetTweetID = targetTweetID, } ); ExtractionRunResponse job = await client.Extractions.Run( new ExtractionRunParams { ToolType = ExtractionRunParamsToolType.ReplyExtractor, TargetTweetID = targetTweetID, } ); using StreamWriter writer = File.CreateText("xquik-replies.jsonl"); ExtractionRetrieveResponse page = await client.Extractions.Retrieve( job.ID, new ExtractionRetrieveParams { Cursor = cursor, Limit = 1000 } ); foreach (var reply in page.Results) { await writer.WriteLineAsync(JsonSerializer.Serialize(reply)); } using HttpResponse csvResponse = await client.Extractions.ExportResults( job.ID, new ExtractionExportResultsParams { Format = Format.Csv } ); await File.WriteAllTextAsync("xquik-replies.csv", await csvResponse.ReadAsString()); ``` Keep the same `estimate.Allowed` credit branch from the follower export workflow before calling `Run`. Store `job.ID` on the queue job, ticket, or warehouse batch before polling so another worker can resume with `client.Extractions.Retrieve`. Keep `page.NextCursor` as the checkpoint when you stream replies to JSON Lines. Pass it back as `Cursor` on the next `ExtractionRetrieveParams` call. Repeat the export with `new ExtractionExportResultsParams { Format = Format.Json }` and `File.WriteAllTextAsync("xquik-replies.json", await jsonResponse.ReadAsString())`. For XLSX, use `new ExtractionExportResultsParams { Format = Format.Xlsx }`, `File.Create("xquik-replies.xlsx")`, and stream-copy the response body. 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. ## Workflow: Post Media Tweets and DM Attachments Use this workflow when a .NET worker 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 `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 `MediaIds` handoff on `client.X.Dm.Send`. ```csharp theme={null} using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text.Json; using System.Threading.Tasks; using XTwitterScraper; using XTwitterScraper.Core; using XTwitterScraper.Models.X.Dm; using XTwitterScraper.Models.X.Media; using XTwitterScraper.Models.X.Tweets; XTwitterScraperClient client = new(); string parentTweetID = "1893704267862470862"; using HttpResponse tweetResponse = await client.X.Tweets.WithRawResponse.Create( new TweetCreateParams { Account = "@username", Text = "Shipping the weekly X API video.", Media = new[] { "https://static.example.com/reports/x-api-export.mp4" }, } ); Dictionary tweetHandoff = await CreateTweetHandoff( tweetResponse, new Dictionary { ["account"] = "@username", ["media_url"] = "https://static.example.com/reports/x-api-export.mp4", } ); using HttpResponse replyResponse = await client.X.Tweets.WithRawResponse.Create( new TweetCreateParams { Account = "@username", Text = "Here is the chart behind the update.", Media = new[] { "https://static.example.com/reports/reply-chart.png" }, ReplyToTweetID = parentTweetID, } ); Dictionary replyHandoff = await CreateTweetHandoff( replyResponse, new Dictionary { ["account"] = "@username", ["reply_to_tweet_id"] = parentTweetID, ["media_url"] = "https://static.example.com/reports/reply-chart.png", } ); await using FileStream localFile = File.OpenRead("handoff.png"); MediaUploadResponse media = await client.X.Media.Upload( new MediaUploadParams { Account = "@username", File = localFile, } ); DmSendResponse dm = await client.X.Dm.Send( "44196397", new DmSendParams { Account = "@username", Text = "Here is the requested asset.", MediaIds = new[] { media.MediaID }, } ); Dictionary dmHandoff = new() { ["message_id"] = dm.MessageID, ["media_id"] = media.MediaID, ["user_id"] = "44196397", ["account"] = "@username", }; await Console.Out.WriteLineAsync(JsonSerializer.Serialize(tweetHandoff)); await Console.Out.WriteLineAsync(JsonSerializer.Serialize(replyHandoff)); await Console.Out.WriteLineAsync(JsonSerializer.Serialize(dmHandoff)); static async Task> CreateTweetHandoff( HttpResponse response, Dictionary row ) { string body = await response.ReadAsString(); using JsonDocument document = JsonDocument.Parse(body); JsonElement payload = document.RootElement; JsonElement billing = payload.GetProperty("billing"); JsonElement result = payload.GetProperty("result"); bool terminal = payload.GetProperty("terminal").GetBoolean(); row["status"] = payload.GetProperty("status").GetString(); row["terminal"] = terminal; row["safe_to_retry"] = payload.GetProperty("safeToRetry").GetBoolean(); row["write_action_id"] = payload.GetProperty("id").GetString(); row["request_hash"] = payload.GetProperty("request").GetProperty("hash").GetString(); row["tweet_id"] = result.ValueKind == JsonValueKind.Object ? result.GetProperty("id").GetString() : null; row["charged"] = billing.GetProperty("charged").GetBoolean(); row["charged_credits"] = billing.GetProperty("chargedCredits").GetString(); row["poll"] = terminal ? null : payload.GetProperty("statusUrl").GetString(); if (row["tweet_id"] is null && payload.TryGetProperty("tweetId", out JsonElement tweetId)) { row["tweet_id"] = tweetId.GetString(); } return row; } ``` Use `client.X.Tweets.WithRawResponse.Create` to capture the durable write action. Send a unique `Idempotency-Key`, store `id`, `request.hash`, `billing`, `result`, and `statusUrl`, then poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. Keep DM body text in private systems. Shared logs and handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and lifecycle status. Leave `ReplyToMessageID` unset because the REST endpoint rejects DM reply threading. Use public media URLs with `client.X.Tweets.Create`. Do not pass uploaded `media.MediaID` values to tweet creation. ## Error Handling The C# SDK throws generated exception types for non-success responses and connection failures. Throws `XTwitterScraperBadRequestException`. Throws `XTwitterScraperUnauthorizedException`. Throws `XTwitterScraperForbiddenException`. Throws `XTwitterScraperNotFoundException`. Throws `XTwitterScraperUnprocessableEntityException`. Throws `XTwitterScraperRateLimitException`. Throws `XTwitterScraper5xxException`. Use the [error handling guide](/guides/error-handling) for response body fields and retry recommendations. ## Pagination Paginated responses expose generated fields such as `HasNextPage`. Pass cursor parameters from the previous response when an endpoint supports cursor pagination. ```csharp theme={null} if (tweets.HasNextPage) { await Console.Error.WriteLineAsync("More results are available"); } ``` ## Webhooks & References * [Search Tweets](/api-reference/x/search-tweets) * [Create Tweet](/api-reference/x-write/create-tweet) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Create Monitor](/api-reference/monitors/create) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [REST API Overview](/api-reference/overview) * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-csharp) # Go SDK for Tweet Search, Exports & X Automation Source: https://docs.xquik.com/sdks/go Use the Xquik Go SDK to search tweets, export JSON Lines, CSV, or XLSX, post media tweets, upload media, send DMs, and run Go X API workers. See code examples.
For the complete documentation index, see llms.txt.
Use typed Go requests to search tweets and export followers or replies. Send JSON Lines, CSV, or XLSX files to workers, queues, and analysts. | Go task | SDK call | Save | | ---------------- | ------------------------ | ------------ | | Search tweets | `client.X.Tweets.Search` | `NextCursor` | | Export followers | `client.Extractions.Run` | `job.ID` | ## Install ```bash theme={null} go get github.com/Xquik-dev/x-twitter-scraper-go ``` ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` ```go theme={null} client := xtwitterscraper.NewClient( option.WithAPIKey(os.Getenv("X_TWITTER_SCRAPER_API_KEY")), ) ``` ## Basic Example Search tweets and write durable JSON Lines handoff rows: ```go theme={null} package main import ( "context" "encoding/json" "os" "github.com/Xquik-dev/x-twitter-scraper-go" "github.com/Xquik-dev/x-twitter-scraper-go/option" ) func main() { client := xtwitterscraper.NewClient( option.WithAPIKey(os.Getenv("X_TWITTER_SCRAPER_API_KEY")), ) page, err := client.X.Tweets.Search(context.Background(), xtwitterscraper.XTweetSearchParams{ Q: "from:username webhook OR SDK", Limit: xtwitterscraper.Int(10), }) if err != nil { panic(err) } encoder := json.NewEncoder(os.Stdout) for _, tweet := range page.Tweets { row := map[string]string{ "tweet_id": tweet.ID, "text": tweet.Text, "author_username": tweet.Author.Username, "created_at": tweet.CreatedAt, } if err := encoder.Encode(row); err != nil { panic(err) } } } ``` ## Workflow: Search Tweets to JSON Lines, CSV, or XLSX This job is for backend workers that need searchable tweet data in a stable handoff format for queues, data lakes, analyst CSV files, or XLSX workbooks. It calls `GET /x/tweets/search` through `client.X.Tweets.Search`, requests the newest matching tweets, and writes each returned tweet as one JSON object per line. ```go theme={null} package main import ( "context" "encoding/json" "os" "github.com/Xquik-dev/x-twitter-scraper-go" "github.com/Xquik-dev/x-twitter-scraper-go/option" ) func main() { client := xtwitterscraper.NewClient( option.WithAPIKey(os.Getenv("X_TWITTER_SCRAPER_API_KEY")), ) out, err := os.Create("xquik-tweet-search.jsonl") if err != nil { panic(err) } defer out.Close() encoder := json.NewEncoder(out) cursor := "" for { params := xtwitterscraper.XTweetSearchParams{ Q: "from:username webhook OR SDK", QueryType: xtwitterscraper.XTweetSearchParamsQueryTypeLatest, } if cursor != "" { params.Cursor = xtwitterscraper.String(cursor) } page, err := client.X.Tweets.Search(context.Background(), params) if err != nil { panic(err) } for _, tweet := range page.Tweets { if err := encoder.Encode(tweet); err != nil { panic(err) } } if !page.HasNextPage || page.NextCursor == "" { break } cursor = page.NextCursor } } ``` The example builds an `XTweetSearchParams` request so the Go fields stay aligned with `GET /x/tweets/search`. The generated params map directly to the REST endpoint: Go field `Q` maps to REST `q`. Use it for the required X search query with keywords, handles, hashtags, or operators. Go field `Limit` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.HasNextPage` is true, keep the same `Q`, filters, `QueryType`, and `Limit` when you continue with `page.NextCursor`. Go field `Cursor` maps to REST `cursor`. Pass the opaque cursor from `NextCursor` to request the next page. Go field `SinceTime` maps to REST `sinceTime`. Use the inclusive ISO bound on every page. Go field `UntilTime` maps to REST `untilTime`. Use the exclusive ISO bound on every page. Go field `QueryType` maps to REST `queryType`. Use `Latest` for chronological results or `Top` for engagement-ranked results. ## Returned Data & Handoff `client.X.Tweets.Search` returns `PaginatedTweets`: JSON field `tweets`. Contains tweet records with `ID`, `Text`, `Author`, `CreatedAt`, `LikeCount`, `ReplyCount`, `RetweetCount`, `QuoteCount`, `BookmarkCount`, `ViewCount`, and `IsNoteTweet` when available. Go field `HasNextPage`. JSON field `has_next_page`. Tells your worker whether another page exists. JSON field `next_cursor`. Store it only when `page.HasNextPage` is true. For bounded pulls that return fewer tweets than `Limit`, pass it back as `Cursor` with the same query, filters, `QueryType`, and `Limit`. Write `Tweets` as JSON Lines to `xquik-tweet-search.jsonl` for queues and data lakes, transform the projected records into CSV for analysts, or produce XLSX from those rows when account teams need spreadsheets. Pass `ID`, `Text`, `Author.Username`, `CreatedAt`, and engagement counts into your CRM or enrichment pipeline. For explicit `Limit` pulls, resume with the same query, filters, `QueryType`, and `Limit`; only `Cursor` changes. ## Workflow: Follower Export to CSV, JSON, or XLSX Use this workflow when a Go worker needs an owned follower list for a CRM import, warehouse load, analyst CSV file, XLSX workbook, or resumable JSON handoff. It calls `POST /extractions/estimate` through `client.Extractions.EstimateCost`, creates the job with `client.Extractions.Run`, reads saved rows with `client.Extractions.Get`, and downloads files with `client.Extractions.ExportResults`. `client.Extractions.Run` returns the queued `202 Accepted` receipt from `POST /extractions`: REST `id`, `toolType`, and `status: "running"` as Go `job.ID`, `job.ToolType`, and `job.Status`. Store `job.ID` immediately, then poll `client.Extractions.Get` before reading pages or calling `client.Extractions.ExportResults`. Credit reservation happens after the job starts. If available credits changed since `EstimateCost`, the run can fetch only the affordable count before export or mark the job `failed` with `insufficient_credits`. ```go theme={null} package main import ( "context" "encoding/json" "io" "os" "time" "github.com/Xquik-dev/x-twitter-scraper-go" "github.com/Xquik-dev/x-twitter-scraper-go/option" ) func main() { ctx := context.Background() client := xtwitterscraper.NewClient( option.WithAPIKey(os.Getenv("X_TWITTER_SCRAPER_API_KEY")), ) targetUsername := "username" estimate, err := client.Extractions.EstimateCost(ctx, xtwitterscraper.ExtractionEstimateCostParams{ ToolType: xtwitterscraper.ExtractionEstimateCostParamsToolTypeFollowerExplorer, TargetUsername: xtwitterscraper.String(targetUsername), }) if err != nil { panic(err) } if !estimate.Allowed { panic("insufficient credits for follower export") } job, err := client.Extractions.Run(ctx, xtwitterscraper.ExtractionRunParams{ ToolType: xtwitterscraper.ExtractionRunParamsToolTypeFollowerExplorer, TargetUsername: xtwitterscraper.String(targetUsername), }) if err != nil { panic(err) } for { page, err := client.Extractions.Get(ctx, job.ID, xtwitterscraper.ExtractionGetParams{ Limit: xtwitterscraper.Int(1000), }) if err != nil { panic(err) } status, _ := page.Job["status"].(string) if status == "completed" { break } if status == "failed" { panic("follower export failed") } time.Sleep(10 * time.Second) } writeRows(ctx, client, job.ID, "xquik-followers.jsonl") writeExport(ctx, client, job.ID, xtwitterscraper.ExtractionExportResultsParamsFormatCsv, "xquik-followers.csv") writeExport(ctx, client, job.ID, xtwitterscraper.ExtractionExportResultsParamsFormatJson, "xquik-followers.json") writeExport(ctx, client, job.ID, xtwitterscraper.ExtractionExportResultsParamsFormatXlsx, "xquik-followers.xlsx") } func writeRows( ctx context.Context, client xtwitterscraper.Client, jobID string, filename string, ) { out, err := os.Create(filename) if err != nil { panic(err) } defer out.Close() encoder := json.NewEncoder(out) cursor := "" for { params := xtwitterscraper.ExtractionGetParams{ Limit: xtwitterscraper.Int(1000), } if cursor != "" { params.Cursor = xtwitterscraper.String(cursor) } page, err := client.Extractions.Get(ctx, jobID, params) if err != nil { panic(err) } for _, row := range page.Results { if err := encoder.Encode(row); err != nil { panic(err) } } if !page.HasMore || page.NextCursor == "" { break } cursor = page.NextCursor } } func writeExport( ctx context.Context, client xtwitterscraper.Client, jobID string, format xtwitterscraper.ExtractionExportResultsParamsFormat, filename string, ) { response, err := client.Extractions.ExportResults(ctx, jobID, xtwitterscraper.ExtractionExportResultsParams{ Format: format, }) if err != nil { panic(err) } defer response.Body.Close() out, err := os.Create(filename) if err != nil { panic(err) } defer out.Close() if _, err := io.Copy(out, response.Body); err != nil { panic(err) } } ``` `follower_explorer` requires `TargetUsername`. Persist `job.ID`, `targetUsername`, `estimate.EstimatedResults`, and `estimate.Source` before polling so a worker restart can resume the same follower export. `client.Extractions.Get` returns `Results`, `HasMore`, and `NextCursor`; pass `NextCursor` back as `Cursor` when you need stored JSON pages before exporting files. 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. Map exported `User ID` or row `xUserId` as the CRM unique key. Cost: 1 credit per follower extracted or returned. Exports are free after the extraction job exists. ## Workflow: Tweet Replies to CSV, JSON, or XLSX Use this workflow when a Go worker, queue consumer, or agent service needs every reply under one tweet as a saved extraction, JSON Lines handoff, or CSV/JSON/XLSX file export. It calls `POST /extractions/estimate` through `client.Extractions.EstimateCost`, creates the job with `client.Extractions.Run`, reads rows with `client.Extractions.Get`, and downloads files with `client.Extractions.ExportResults`. Reuse the polling, `writeRows`, and `writeExport` helpers from the follower workflow. Only the tool type, target field, and output filenames change: ```go theme={null} targetTweetID := "1893704267862470862" estimate, err := client.Extractions.EstimateCost(ctx, xtwitterscraper.ExtractionEstimateCostParams{ ToolType: xtwitterscraper.ExtractionEstimateCostParamsToolTypeReplyExtractor, TargetTweetID: xtwitterscraper.String(targetTweetID), }) if err != nil { panic(err) } if !estimate.Allowed { panic("insufficient credits for reply extraction") } job, err := client.Extractions.Run(ctx, xtwitterscraper.ExtractionRunParams{ ToolType: xtwitterscraper.ExtractionRunParamsToolTypeReplyExtractor, TargetTweetID: xtwitterscraper.String(targetTweetID), }) if err != nil { panic(err) } // Run the same polling loop from the follower workflow before exporting rows. writeRows(ctx, client, job.ID, "xquik-replies.jsonl") writeExport(ctx, client, job.ID, xtwitterscraper.ExtractionExportResultsParamsFormatCsv, "xquik-replies.csv") writeExport(ctx, client, job.ID, xtwitterscraper.ExtractionExportResultsParamsFormatJson, "xquik-replies.json") writeExport(ctx, client, job.ID, xtwitterscraper.ExtractionExportResultsParamsFormatXlsx, "xquik-replies.xlsx") ``` `reply_extractor` requires `TargetTweetID`. `client.Extractions.Get` returns `Results`, `HasMore`, and `NextCursor`; the shared `writeRows` helper passes `NextCursor` back as `Cursor` until all stored rows are written. 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. `client.Extractions.ExportResults` supports CSV, JSON, and XLSX for file handoff. Cost: 1 credit per reply extracted or returned. ## Workflow: Post Media Tweets and DM Attachments Use this workflow when a Go worker, queue consumer, or agent service needs to post a media-backed tweet, reply with media, or send one uploaded media item in a DM. Tweet and reply media posts use public media URLs directly on `client.X.Tweets.New`. Send up to 4 image URLs or exactly 1 MP4 video URL up to 100 MB. Do not mix video with other media. Do not upload first when the media URL is already public. ```go theme={null} type writeActionBilling struct { Charged bool `json:"charged"` ChargedCredits string `json:"chargedCredits"` } type writeActionRequest struct { Hash *string `json:"hash"` } type writeActionResult struct { ID string `json:"id"` } type tweetCreatePayload struct { ID string `json:"id"` Status string `json:"status"` Terminal bool `json:"terminal"` SafeToRetry bool `json:"safeToRetry"` StatusURL string `json:"statusUrl"` Request writeActionRequest `json:"request"` Billing writeActionBilling `json:"billing"` Result *writeActionResult `json:"result"` TweetID string `json:"tweetId"` } func createTweetHandoff(action tweetCreatePayload, base map[string]any) map[string]any { var resultID any if action.Result != nil { resultID = action.Result.ID } else if action.TweetID != "" { resultID = action.TweetID } poll := any(nil) if !action.Terminal { poll = action.StatusURL } handoff := map[string]any{ "status": action.Status, "terminal": action.Terminal, "safe_to_retry": action.SafeToRetry, "write_action_id": action.ID, "request_hash": action.Request.Hash, "tweet_id": resultID, "charged": action.Billing.Charged, "charged_credits": action.Billing.ChargedCredits, "poll": poll, } for key, value := range base { handoff[key] = value } return handoff } ``` Use `option.WithResponseBodyInto` to capture the durable write action. Send a unique `Idempotency-Key`, store `id`, `request.hash`, `billing`, `result`, and `statusUrl`, then poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. ```go theme={null} ctx := context.Background() var tweetPayload tweetCreatePayload _, err := client.X.Tweets.New(ctx, xtwitterscraper.XTweetNewParams{ Account: "@username", Text: xtwitterscraper.String("New demo video is live."), Media: []string{"https://example.com/product-demo.mp4"}, }, option.WithResponseBodyInto(&tweetPayload)) if err != nil { panic(err) } tweetHandoff := createTweetHandoff(tweetPayload, map[string]any{ "account": "@username", "media": []string{"https://example.com/product-demo.mp4"}, }) if err := json.NewEncoder(os.Stdout).Encode(tweetHandoff); err != nil { panic(err) } ``` To post an image reply, add `ReplyToTweetID`: ```go theme={null} var replyPayload tweetCreatePayload _, err := client.X.Tweets.New(ctx, xtwitterscraper.XTweetNewParams{ Account: "@username", Text: xtwitterscraper.String("Here is the requested screenshot."), ReplyToTweetID: xtwitterscraper.String("1893704267862470862"), Media: []string{"https://example.com/export-preview.png"}, }, option.WithResponseBodyInto(&replyPayload)) if err != nil { panic(err) } replyHandoff := createTweetHandoff(replyPayload, map[string]any{ "account": "@username", "reply_to_tweet_id": "1893704267862470862", "media": []string{"https://example.com/export-preview.png"}, }) if err := json.NewEncoder(os.Stdout).Encode(replyHandoff); err != nil { panic(err) } ``` For DM attachments, upload the local file first and pass the returned `media.MediaID` as the only `MediaIDs` item: ```go theme={null} file, err := os.Open("./handoff.png") if err != nil { panic(err) } defer file.Close() media, err := client.X.Media.Upload(context.Background(), xtwitterscraper.XMediaUploadParams{ Account: "@username", File: file, }) if err != nil { panic(err) } dm, err := client.X.Dm.Send(context.Background(), "44196397", xtwitterscraper.XDmSendParams{ Account: "@username", Text: "Here is the asset.", MediaIDs: []string{media.MediaID}, }) if err != nil { panic(err) } dmHandoff := map[string]any{ "status": "sent", "message_id": dm.MessageID, "media_id": media.MediaID, "account": "@username", "user_id": "44196397", } if err := json.NewEncoder(os.Stdout).Encode(dmHandoff); err != nil { panic(err) } ``` `client.X.Tweets.New` returns `TweetID` for confirmed posts. Raw create responses can also include the pending write fields above when confirmation is still running. `client.X.Media.Upload` returns `media.MediaID` for DM attachments, and `client.X.Dm.Send` returns `dm.MessageID` for support tickets, CRM records, queue jobs, or agent memory. Keep DM body text in private systems. Shared logs, public artifacts, queue status, and agent handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and send status instead of full DM bodies. Leave `ReplyToMessageID` unset even if generated SDK params expose it; the REST endpoint rejects DM reply threading. Text-only tweet and reply writes cost 30 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 `MediaID` values to `client.X.Tweets.New`; that method uses `Media` with public media URLs. ## Cost, Limits & Retries Tweet search costs 1 credit per tweet returned. If remaining credits cannot cover the requested page, the API can return fewer tweets than `Limit`; if 0 paid results are affordable, it returns `402 insufficient_credits`. Read calls are rate-limited, and `429` responses include `Retry-After`. Generated Go methods return `error` for connection failures and non-success responses. Retry connection errors, 408, 409, 429, and 5xx responses with backoff. Do not retry 400, 401, 403, 404, or 422 until the request, authentication, permission, or input issue is fixed. ## Error Handling Generated Go methods return `error` for connection failures and non-success API responses. Check the concrete error type from the SDK when you need status-code specific handling, and use the [error handling guide](/guides/error-handling) for response semantics. Common retryable cases are connection errors, 408, 409, 429, and 5xx responses. ## Pagination List and search responses expose generated pagination fields such as `HasNextPage`. Pass cursor parameters from the previous response when the endpoint supports cursor pagination. ```go theme={null} if tweets.HasNextPage { fmt.Println("More results are available") } ``` ## Webhooks & References * [Search Tweets](/api-reference/x/search-tweets) * [Create Tweet](/api-reference/x-write/create-tweet) * [Get Write Action Status](/api-reference/x-write/get-write-action-status) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [Twitter Scraper API Overview](/api-reference/overview) * Download the OpenAPI schema: `curl -o openapi.json https://xquik.com/openapi.json` * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-go) # Java SDK for Tweet Search, Exports & X Automation Source: https://docs.xquik.com/sdks/java Use the Xquik Java SDK to search tweets, export JSON Lines, CSV, or XLSX, post media tweets, upload media, send DMs, and run JVM X API workers. See examples.
For the complete documentation index, see llms.txt.
Use the Java SDK for generated JVM models, builders, sync calls, async calls, typed exceptions, retries, and file upload helpers in Java 8+ applications. It fits JVM services, Spring workers, queue consumers, and cron jobs. Search tweets, export followers, monitor tweets, post media, upload files, or send direct messages. Hand tweet, follower, reply, and profile rows to analytics or CRM systems. | Java worker task | SDK call | Durable checkpoint | | ---------------------------------- | ---------------------------------------------- | ------------------------------------------------ | | Search tweets | `client.x().tweets().search` | Persist `page.nextCursor()` after every page. | | Estimate follower or reply exports | `client.extractions().estimateCost` | Store the estimate and `allowed()` decision. | | Start an extraction | `client.extractions().run` | Save `job.id()` immediately. | | Retrieve saved rows | `client.extractions().retrieve` | Keep `nextCursor()` while `hasMore()` is true. | | Export CSV, JSON, or XLSX | `client.extractions().exportResults` | Record the job ID, format, and file destination. | | Create a tweet | `client.x().tweets().withRawResponse().create` | Store the write action ID and terminal state. | ## Install Maven Central publication is pending. Build from source until the `com.x_twitter_scraper.api:x-twitter-scraper-java` artifact resolves in Maven Central. ```bash theme={null} git clone https://github.com/Xquik-dev/x-twitter-scraper-java.git cd x-twitter-scraper-java ./gradlew build ``` For local Maven testing: ```bash theme={null} ./gradlew publishToMavenLocal -PpublishLocal ``` Before restoring Maven Central install snippets, verify: ```bash theme={null} curl -f https://repo1.maven.org/maven2/com/x_twitter_scraper/api/x-twitter-scraper-java/maven-metadata.xml ``` ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` `XTwitterScraperOkHttpClient.fromEnv()` reads `X_TWITTER_SCRAPER_API_KEY`, `X_TWITTER_SCRAPER_BEARER_TOKEN`, and `X_TWITTER_SCRAPER_BASE_URL`. ## Basic Example Search tweets and write durable JSON Lines handoff rows: ```java theme={null} import com.fasterxml.jackson.databind.ObjectMapper; import com.x_twitter_scraper.api.client.XTwitterScraperClient; import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient; import com.x_twitter_scraper.api.models.PaginatedTweets; import com.x_twitter_scraper.api.models.SearchTweet; import com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams; import java.util.LinkedHashMap; import java.util.Map; XTwitterScraperClient client = XTwitterScraperOkHttpClient.fromEnv(); ObjectMapper objectMapper = new ObjectMapper(); TweetSearchParams params = TweetSearchParams.builder() .q("from:username webhook OR SDK") .limit(10L) .build(); PaginatedTweets page = client.x().tweets().search(params); for (SearchTweet tweet : page.tweets()) { Map row = new LinkedHashMap<>(); row.put("tweet_id", tweet.id()); row.put("text", tweet.text()); row.put("author_username", tweet.author().map(SearchTweet.Author::username).orElse(null)); row.put("created_at", tweet.createdAt().orElse(null)); System.out.println(objectMapper.writeValueAsString(row)); } ``` ## Workflow: Search Tweets to JSON Lines, CSV, or XLSX Use this workflow when a Java service, Spring Batch job, scheduled worker, or queue consumer 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()`. ```java theme={null} import com.fasterxml.jackson.databind.ObjectMapper; import com.x_twitter_scraper.api.client.XTwitterScraperClient; import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient; import com.x_twitter_scraper.api.models.PaginatedTweets; import com.x_twitter_scraper.api.models.SearchTweet; import com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams; import com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams.QueryType; import java.io.BufferedWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; XTwitterScraperClient client = XTwitterScraperOkHttpClient.fromEnv(); ObjectMapper objectMapper = new ObjectMapper(); String query = "from:username webhook OR SDK"; String cursor = null; int pageIndex = 0; List headers = Arrays.asList( "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" ); try ( BufferedWriter jsonlWriter = Files.newBufferedWriter( Paths.get("xquik-tweet-search.jsonl"), StandardCharsets.UTF_8 ); BufferedWriter csvWriter = Files.newBufferedWriter( Paths.get("xquik-tweet-search.csv"), StandardCharsets.UTF_8 ) ) { writeCsvRow(csvWriter, headers); do { String pageCursor = cursor; TweetSearchParams.Builder builder = TweetSearchParams.builder() .q(query) .queryType(QueryType.LATEST); if (cursor != null && !cursor.isEmpty()) { builder.cursor(cursor); } PaginatedTweets page = client.x().tweets().search(builder.build()); for (SearchTweet tweet : page.tweets()) { Map row = new LinkedHashMap<>(); row.put("source", "xquik.java.search"); row.put("query", query); row.put("tweet_id", tweet.id()); row.put("text", tweet.text()); row.put("author_id", tweet.author().map(SearchTweet.Author::id).orElse(null)); row.put("author_username", tweet.author().map(SearchTweet.Author::username).orElse(null)); row.put("author_name", tweet.author().map(SearchTweet.Author::name).orElse(null)); row.put("created_at", tweet.createdAt().orElse(null)); row.put("like_count", tweet.likeCount().orElse(0L)); row.put("reply_count", tweet.replyCount().orElse(0L)); row.put("retweet_count", tweet.retweetCount().orElse(0L)); row.put("quote_count", tweet.quoteCount().orElse(0L)); row.put("view_count", tweet.viewCount().orElse(0L)); row.put("bookmark_count", tweet.bookmarkCount().orElse(0L)); row.put("is_note_tweet", tweet.isNoteTweet().orElse(false)); row.put("page_index", pageIndex); row.put("page_cursor", pageCursor); row.put("next_cursor", page.hasNextPage() ? page.nextCursor() : null); row.put("has_next_page", page.hasNextPage()); List csvRow = new ArrayList<>(); for (String header : headers) { csvRow.add(row.get(header)); } jsonlWriter.write(objectMapper.writeValueAsString(row)); jsonlWriter.newLine(); writeCsvRow(csvWriter, csvRow); } pageIndex++; cursor = page.hasNextPage() ? page.nextCursor() : null; } while (cursor != null && !cursor.isEmpty()); } static void writeCsvRow(BufferedWriter writer, List values) throws IOException { for (int index = 0; index < values.size(); index++) { if (index > 0) { writer.write(","); } Object value = values.get(index); String cell = value == null ? "" : String.valueOf(value); writer.write("\"" + cell.replace("\"", "\"\"") + "\""); } writer.newLine(); } ``` ### Request Mapping Java 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. Java builder method `.limit()` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.hasNextPage()` is true, keep the same `.q()`, filters, `.queryType()`, and `.limit()` when you continue with `page.nextCursor()`. Java builder method `.cursor()` maps to REST `cursor`. Pass the opaque cursor from `page.nextCursor()` to request the next page. Java builder method `.sinceTime()` maps to REST `sinceTime`. Use it as the ISO 8601 lower bound for tweet creation time. Java builder method `.untilTime()` maps to REST `untilTime`. Use it as the ISO 8601 upper bound for tweet creation time. Java builder method `.queryType()` maps to REST `queryType`. Use `QueryType.LATEST` for chronological search or `QueryType.TOP` for engagement-ranked search. ### Returned Data & Handoff `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. For bounded pulls that return fewer tweets than the requested `.limit()`, pass `page.nextCursor()` back as `.cursor()` with the same query, filters, `QueryType`, and `.limit()`. 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 explicit `.limit()` pulls, resume with the same query, filters, `QueryType`, and `.limit()`; only `.cursor()` changes. ## Workflow: Follower Export to CSV, JSON, or XLSX Use this workflow when a JVM 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`. `client.extractions().run` returns the queued `202 Accepted` receipt from `POST /extractions`: REST `id`, `toolType`, and `status: "running"` as Java `job.id()`, `job.toolType()`, and `job._status()`. Store `job.id()` immediately, then poll `client.extractions().retrieve` before reading pages or calling `client.extractions().exportResults`. Credit reservation happens after the job starts. If available credits changed since `estimateCost`, the run can fetch only the affordable count before export or mark the job `failed` with `insufficient_credits`. ```java theme={null} import com.fasterxml.jackson.databind.ObjectMapper; import com.x_twitter_scraper.api.client.XTwitterScraperClient; import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient; import com.x_twitter_scraper.api.core.JsonValue; import com.x_twitter_scraper.api.core.http.HttpResponse; import com.x_twitter_scraper.api.models.extractions.ExtractionEstimateCostParams; import com.x_twitter_scraper.api.models.extractions.ExtractionEstimateCostResponse; import com.x_twitter_scraper.api.models.extractions.ExtractionExportResultsParams; import com.x_twitter_scraper.api.models.extractions.ExtractionRetrieveParams; import com.x_twitter_scraper.api.models.extractions.ExtractionRetrieveResponse; import com.x_twitter_scraper.api.models.extractions.ExtractionRunParams; import com.x_twitter_scraper.api.models.extractions.ExtractionRunResponse; import java.io.BufferedWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; XTwitterScraperClient client = XTwitterScraperOkHttpClient.fromEnv(); ObjectMapper objectMapper = new ObjectMapper(); String targetUsername = "username"; ExtractionEstimateCostResponse estimate = client.extractions().estimateCost( ExtractionEstimateCostParams.builder() .toolType(ExtractionEstimateCostParams.ToolType.FOLLOWER_EXPLORER) .targetUsername(targetUsername) .build() ); if (!estimate.allowed()) { throw new IllegalStateException( "Follower export requires " + estimate.creditsRequired() + " credits." ); } ExtractionRunResponse job = client.extractions().run( ExtractionRunParams.builder() .toolType(ExtractionRunParams.ToolType.FOLLOWER_EXPLORER) .targetUsername(targetUsername) .build() ); boolean completed = false; for (int attempt = 0; attempt < 120; attempt++) { ExtractionRetrieveResponse statusPage = client.extractions().retrieve(job.id()); JsonValue statusValue = statusPage.job()._additionalProperties().get("status"); String status = statusValue == null ? "unknown" : statusValue.asString().orElse("unknown"); if ("completed".equals(status)) { completed = true; break; } if ("failed".equals(status)) { throw new IllegalStateException("Follower export failed."); } Thread.sleep(10_000L); } if (!completed) { throw new IllegalStateException("Follower export did not complete before timeout."); } String cursor = null; try (BufferedWriter writer = Files.newBufferedWriter( Paths.get("xquik-followers.jsonl"), StandardCharsets.UTF_8 )) { do { ExtractionRetrieveParams.Builder pageParams = ExtractionRetrieveParams.builder() .id(job.id()) .limit(1000L); if (cursor != null && !cursor.isEmpty()) { pageParams.cursor(cursor); } ExtractionRetrieveResponse page = client.extractions().retrieve(pageParams.build()); for (ExtractionRetrieveResponse.Result row : page.results()) { writer.write(objectMapper.writeValueAsString(row._additionalProperties())); writer.newLine(); } cursor = page.hasMore() ? page.nextCursor().orElse(null) : null; } while (cursor != null && !cursor.isEmpty()); } try (HttpResponse export = client.extractions().exportResults( ExtractionExportResultsParams.builder() .id(job.id()) .format(ExtractionExportResultsParams.Format.CSV) .build() )) { Files.copy( export.body(), Paths.get("xquik-followers.csv"), StandardCopyOption.REPLACE_EXISTING ); } try (HttpResponse export = client.extractions().exportResults( ExtractionExportResultsParams.builder() .id(job.id()) .format(ExtractionExportResultsParams.Format.JSON) .build() )) { Files.copy( export.body(), Paths.get("xquik-followers.json"), StandardCopyOption.REPLACE_EXISTING ); } try (HttpResponse export = client.extractions().exportResults( ExtractionExportResultsParams.builder() .id(job.id()) .format(ExtractionExportResultsParams.Format.XLSX) .build() )) { Files.copy( export.body(), Paths.get("xquik-followers.xlsx"), StandardCopyOption.REPLACE_EXISTING ); } ``` Persist `job.id()`, `targetUsername`, `estimate.estimatedResults()`, and `estimate.source()` before polling so retries can resume the extraction. `client.extractions().retrieve` returns `results()`, `hasMore()`, and `nextCursor()`; pass `nextCursor()` back as `cursor` 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. ## Workflow: Tweet Replies to CSV, JSON, or XLSX Use this workflow when a Java 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`. Reuse the follower export loop above. Change only the required target, tool type, and output names: ```java theme={null} String targetTweetId = "1893704267862470862"; ExtractionEstimateCostResponse estimate = client.extractions().estimateCost( ExtractionEstimateCostParams.builder() .toolType(ExtractionEstimateCostParams.ToolType.REPLY_EXTRACTOR) .targetTweetId(targetTweetId) .build() ); ExtractionRunResponse job = client.extractions().run( ExtractionRunParams.builder() .toolType(ExtractionRunParams.ToolType.REPLY_EXTRACTOR) .targetTweetId(targetTweetId) .build() ); ExtractionRetrieveParams.Builder pageParams = ExtractionRetrieveParams.builder() .id(job.id()) .limit(1000L); if (cursor != null && !cursor.isEmpty()) { pageParams.cursor(cursor); } ExtractionRetrieveResponse page = client.extractions().retrieve(pageParams.build()); ``` Persist `job.id()` before polling so a queue retry can resume with `client.extractions().retrieve(job.id())` and repeat `client.extractions().exportResults(...)` after completion. Stream `page.results()` to `Paths.get("xquik-replies.jsonl")`. Keep `page.nextCursor()` as the checkpoint while streaming replies to JSON Lines. Pass it back as `cursor` on the next `ExtractionRetrieveParams.Builder pageParams`. Keep the same `estimate.allowed()` credit branch from the follower export workflow before calling `run`. Export the completed job with the same helper used above: ```java theme={null} try (HttpResponse export = client.extractions().exportResults( ExtractionExportResultsParams.builder() .id(job.id()) .format(ExtractionExportResultsParams.Format.CSV) .build() )) { Files.copy( export.body(), Paths.get("xquik-replies.csv"), StandardCopyOption.REPLACE_EXISTING ); } ``` Repeat the export with `ExtractionExportResultsParams.Format.JSON` to write `Paths.get("xquik-replies.json")` and `ExtractionExportResultsParams.Format.XLSX` to write `Paths.get("xquik-replies.xlsx")`. 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. ## Workflow: Post Media Tweets and DM Attachments Use this workflow when a JVM 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. ```java theme={null} import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.x_twitter_scraper.api.client.XTwitterScraperClient; import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient; import com.x_twitter_scraper.api.core.http.HttpResponseFor; import com.x_twitter_scraper.api.models.x.dm.DmSendParams; import com.x_twitter_scraper.api.models.x.dm.DmSendResponse; import com.x_twitter_scraper.api.models.x.media.MediaUploadParams; import com.x_twitter_scraper.api.models.x.media.MediaUploadResponse; import com.x_twitter_scraper.api.models.x.tweets.TweetCreateParams; import com.x_twitter_scraper.api.models.x.tweets.TweetCreateResponse; import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; XTwitterScraperClient client = XTwitterScraperOkHttpClient.fromEnv(); ObjectMapper objectMapper = new ObjectMapper(); static Map createTweetHandoff( Map payload, Map base ) { Map handoff = new LinkedHashMap<>(); Map billing = (Map) payload.get("billing"); Map request = (Map) payload.get("request"); Map result = (Map) payload.get("result"); boolean terminal = Boolean.TRUE.equals(payload.get("terminal")); handoff.put("status", payload.get("status")); handoff.put("terminal", terminal); handoff.put("safe_to_retry", payload.get("safeToRetry")); handoff.put("write_action_id", payload.get("id")); handoff.put("request_hash", request.get("hash")); handoff.put("tweet_id", result == null ? payload.get("tweetId") : result.get("id")); handoff.put("charged", billing.get("charged")); handoff.put("charged_credits", billing.get("chargedCredits")); handoff.put("poll", terminal ? null : payload.get("statusUrl")); handoff.putAll(base); return handoff; } Map tweetPayload; try (HttpResponseFor response = client.x().tweets().withRawResponse().create( TweetCreateParams.builder() .account("@username") .text("Shipping the weekly X API video.") .addMedia("https://static.example.com/reports/x-api-export.mp4") .build() )) { tweetPayload = objectMapper.readValue( response.body(), new TypeReference>() {} ); } Map tweetBase = new LinkedHashMap<>(); tweetBase.put("account", "@username"); tweetBase.put("media", List.of("https://static.example.com/reports/x-api-export.mp4")); Map tweetHandoff = createTweetHandoff(tweetPayload, tweetBase); Map replyPayload; try (HttpResponseFor response = client.x().tweets().withRawResponse().create( TweetCreateParams.builder() .account("@username") .text("Here is the chart behind the update.") .addMedia("https://static.example.com/reports/reply-chart.png") .replyToTweetId("1893704267862470862") .build() )) { replyPayload = objectMapper.readValue( response.body(), new TypeReference>() {} ); } Map replyBase = new LinkedHashMap<>(); replyBase.put("account", "@username"); replyBase.put("reply_to_tweet_id", "1893704267862470862"); replyBase.put("media", List.of("https://static.example.com/reports/reply-chart.png")); Map replyHandoff = createTweetHandoff(replyPayload, replyBase); MediaUploadResponse media = client.x().media().upload( MediaUploadParams.builder() .account("@username") .file(Paths.get("handoff.png")) .build() ); DmSendResponse dm = client.x().dm().send( "44196397", DmSendParams.builder() .account("@username") .text("Here is the requested asset.") .addMediaId(media.mediaId()) .build() ); Map dmHandoff = new LinkedHashMap<>(); dmHandoff.put("status", "sent"); dmHandoff.put("message_id", dm.messageId()); dmHandoff.put("media_id", media.mediaId()); dmHandoff.put("account", "@username"); dmHandoff.put("user_id", "44196397"); System.out.println(objectMapper.writeValueAsString(tweetHandoff)); System.out.println(objectMapper.writeValueAsString(replyHandoff)); System.out.println(objectMapper.writeValueAsString(dmHandoff)); ``` Use `client.x().tweets().withRawResponse().create(...)` to capture the durable write action. Send a unique `Idempotency-Key`, store `id`, `request.hash`, `billing`, `result`, and `statusUrl`, then poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. Keep DM body text in private systems. Shared logs and handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and lifecycle status. Leave `replyToMessageId` unset because the REST endpoint rejects DM reply threading. Use public media URLs with `client.x().tweets().create`. Do not pass uploaded `media.mediaId()` values to tweet creation. ## Error Handling The Java SDK throws unchecked exceptions. Throws `BadRequestException`. Throws `UnauthorizedException`. Throws `PermissionDeniedException`. Throws `NotFoundException`. Throws `UnprocessableEntityException`. Throws `RateLimitException`. Throws `InternalServerException`. Connection and I/O failures use `XTwitterScraperIoException`. All SDK exceptions inherit from `XTwitterScraperException`. ## Pagination Paginated responses expose generated fields such as `hasNextPage`. Use the endpoint cursor fields documented in the API reference when requesting additional pages. ```java theme={null} if (tweets.hasNextPage()) { System.err.println("More results are available"); } ``` ## Webhooks & References * [Search Tweets](/api-reference/x/search-tweets) * [Create Tweet](/api-reference/x-write/create-tweet) * [Get Write Action Status](/api-reference/x-write/get-write-action-status) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Create Monitor](/api-reference/monitors/create) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [REST API Overview](/api-reference/overview) * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-java) # Kotlin SDK for Tweet Search, CSV & X Automation Source: https://docs.xquik.com/sdks/kotlin Use Xquik's Kotlin SDK to search tweets, export JSON Lines, CSV, or XLSX, post media tweets, upload media, send DMs, and build durable JVM X API workers.
For the complete documentation index, see llms.txt.
## Distinct Kotlin Fit Choose Kotlin for nullable models, builders, sync or async calls, and JVM workers. The current path builds from source until Maven Central publication. Use Java for Java-first services and C# for .NET workers. Use the Kotlin SDK for generated JVM models, nullable values, builders, sync calls, async calls, typed exceptions, retries, and file upload helpers. It fits Ktor workers, Spring jobs, queue consumers, and scheduled tasks. Search tweets, export followers, monitor tweets, post media, upload files, or send direct messages. Hand tweet, follower, reply, and profile rows to downstream systems. | Kotlin task | SDK call | Save | | ------------------------- | ---------------------------------------------- | ------------------------------------------------ | | Search tweets | `client.x().tweets().search` | Persist `page.nextCursor()` after every page. | | Estimate exports | `client.extractions().estimateCost` | Store the estimate and `allowed()` decision. | | Start an extraction | `client.extractions().run` | Save `job.id()` immediately. | | Retrieve saved rows | `client.extractions().retrieve` | Keep `nextCursor()` while `hasMore()` is true. | | Export CSV, JSON, or XLSX | `client.extractions().exportResults` | Record the job ID, format, and file destination. | | Create a tweet | `client.x().tweets().withRawResponse().create` | Store the write action ID and terminal state. | ## Install Maven Central publication is pending. Build from source until the `com.x_twitter_scraper.api:x-twitter-scraper-kotlin` artifact resolves in Maven Central. ```bash theme={null} git clone https://github.com/Xquik-dev/x-twitter-scraper-kotlin.git cd x-twitter-scraper-kotlin ./gradlew build ``` For local Maven testing: ```bash theme={null} ./gradlew publishToMavenLocal -PpublishLocal ``` Before restoring Maven Central install snippets, verify: ```bash theme={null} curl -f https://repo1.maven.org/maven2/com/x_twitter_scraper/api/x-twitter-scraper-kotlin/maven-metadata.xml ``` ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` `XTwitterScraperOkHttpClient.fromEnv()` reads `X_TWITTER_SCRAPER_API_KEY`, `X_TWITTER_SCRAPER_BEARER_TOKEN`, and `X_TWITTER_SCRAPER_BASE_URL`. ## Basic Example Search tweets and write durable JSON Lines handoff rows: ```kotlin theme={null} import com.fasterxml.jackson.databind.ObjectMapper import com.x_twitter_scraper.api.client.XTwitterScraperClient import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient import com.x_twitter_scraper.api.models.PaginatedTweets import com.x_twitter_scraper.api.models.SearchTweet import com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams val client: XTwitterScraperClient = XTwitterScraperOkHttpClient.fromEnv() val objectMapper = ObjectMapper() val params: TweetSearchParams = TweetSearchParams.builder() .q("from:username webhook OR SDK") .limit(10L) .build() val page: PaginatedTweets = client.x().tweets().search(params) for (tweet: SearchTweet in page.tweets()) { val row = linkedMapOf( "tweet_id" to tweet.id(), "text" to tweet.text(), "author_username" to tweet.author()?.username(), "created_at" to tweet.createdAt(), ) println(objectMapper.writeValueAsString(row)) } ``` ## 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()`. ```kotlin theme={null} import com.fasterxml.jackson.databind.ObjectMapper import com.x_twitter_scraper.api.client.XTwitterScraperClient import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient import com.x_twitter_scraper.api.models.PaginatedTweets import com.x_twitter_scraper.api.models.SearchTweet import com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams import com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams.QueryType import java.io.BufferedWriter import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Paths val client: XTwitterScraperClient = XTwitterScraperOkHttpClient.fromEnv() val objectMapper = ObjectMapper() val query = "from:username webhook OR SDK" var cursor: String? = null var pageIndex = 0 val 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( "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) { writer.write( values.joinToString(",") { value -> val cell = value?.toString() ?: "" "\"" + cell.replace("\"", "\"\"") + "\"" } ) writer.newLine() } ``` ### Request Mapping 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. Kotlin builder method `.limit()` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.hasNextPage()` is true, keep the same `.q()`, filters, `.queryType()`, and `.limit()` when you continue with `page.nextCursor()`. Kotlin builder method `.cursor()` maps to REST `cursor`. Pass the opaque cursor from `page.nextCursor()` to request the next page. Kotlin builder method `.sinceTime()` maps to REST `sinceTime`. Use it as the ISO 8601 lower bound for tweet creation time. Kotlin builder method `.untilTime()` maps to REST `untilTime`. Use it as the ISO 8601 upper bound for tweet creation time. Kotlin builder method `.queryType()` maps to REST `queryType`. Use `QueryType.LATEST` for chronological search or `QueryType.TOP` for engagement-ranked search. ### Returned Data & Handoff `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. For bounded pulls that return fewer tweets than the requested `.limit()`, pass `page.nextCursor()` back as `.cursor()` with the same query, filters, `QueryType`, and `.limit()`. 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 explicit `.limit()` pulls, resume with the same query, filters, `QueryType`, and `.limit()`; only `.cursor()` changes. ## Workflow: Follower Export to CSV, JSON, or XLSX 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`. `client.extractions().run` returns the queued `202 Accepted` receipt from `POST /extractions`: REST `id`, `toolType`, and `status: "running"` as Kotlin `job.id()`, `job.toolType()`, and `job._status()`. Store `job.id()` immediately, then poll `client.extractions().retrieve` before reading pages or calling `client.extractions().exportResults`. Credit reservation happens after the job starts. If available credits changed since `estimateCost`, the run can fetch only the affordable count before export or mark the job `failed` with `insufficient_credits`. ```kotlin theme={null} import com.fasterxml.jackson.databind.ObjectMapper import com.x_twitter_scraper.api.client.XTwitterScraperClient import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient import com.x_twitter_scraper.api.models.extractions.ExtractionEstimateCostParams import com.x_twitter_scraper.api.models.extractions.ExtractionEstimateCostResponse import com.x_twitter_scraper.api.models.extractions.ExtractionExportResultsParams import com.x_twitter_scraper.api.models.extractions.ExtractionRetrieveParams import com.x_twitter_scraper.api.models.extractions.ExtractionRetrieveResponse import com.x_twitter_scraper.api.models.extractions.ExtractionRunParams import com.x_twitter_scraper.api.models.extractions.ExtractionRunResponse import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Paths import java.nio.file.StandardCopyOption val client: XTwitterScraperClient = XTwitterScraperOkHttpClient.fromEnv() val objectMapper = ObjectMapper() val targetUsername = "username" val estimate: ExtractionEstimateCostResponse = client.extractions().estimateCost( ExtractionEstimateCostParams.builder() .toolType(ExtractionEstimateCostParams.ToolType.FOLLOWER_EXPLORER) .targetUsername(targetUsername) .build() ) if (!estimate.allowed()) { error("Follower export requires ${estimate.creditsRequired()} credits.") } val job: ExtractionRunResponse = client.extractions().run( ExtractionRunParams.builder() .toolType(ExtractionRunParams.ToolType.FOLLOWER_EXPLORER) .targetUsername(targetUsername) .build() ) var completed = false var attempts = 0 while (!completed && attempts < 120) { val statusPage = client.extractions().retrieve(job.id()) val status = statusPage.job()._additionalProperties()["status"]?.asString() ?: "unknown" if (status == "completed") { completed = true } else if (status == "failed") { error("Follower export failed.") } else { Thread.sleep(10_000L) } attempts += 1 } if (!completed) { error("Follower export did not complete before timeout.") } var cursor: String? = null Files.newBufferedWriter(Paths.get("xquik-followers.jsonl"), StandardCharsets.UTF_8).use { writer -> do { val pageParams = ExtractionRetrieveParams.builder() .id(job.id()) .limit(1000L) if (!cursor.isNullOrEmpty()) { pageParams.cursor(cursor) } val page: ExtractionRetrieveResponse = client.extractions().retrieve(pageParams.build()) for (row in page.results()) { writer.write(objectMapper.writeValueAsString(row._additionalProperties())) writer.newLine() } cursor = if (page.hasMore()) page.nextCursor() else null } while (!cursor.isNullOrEmpty()) } client.extractions().exportResults( ExtractionExportResultsParams.builder() .id(job.id()) .format(ExtractionExportResultsParams.Format.CSV) .build() ).use { export -> Files.copy(export.body(), Paths.get("xquik-followers.csv"), StandardCopyOption.REPLACE_EXISTING) } client.extractions().exportResults( ExtractionExportResultsParams.builder() .id(job.id()) .format(ExtractionExportResultsParams.Format.JSON) .build() ).use { export -> Files.copy(export.body(), Paths.get("xquik-followers.json"), StandardCopyOption.REPLACE_EXISTING) } client.extractions().exportResults( ExtractionExportResultsParams.builder() .id(job.id()) .format(ExtractionExportResultsParams.Format.XLSX) .build() ).use { export -> Files.copy(export.body(), Paths.get("xquik-followers.xlsx"), StandardCopyOption.REPLACE_EXISTING) } ``` Persist `job.id()`, `targetUsername`, `estimate.estimatedResults()`, and `estimate.source()` before polling so retries can resume the extraction. `client.extractions().retrieve` returns `results()`, `hasMore()`, and `nextCursor()`; pass `nextCursor()` back as `cursor` 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. ## Workflow: Tweet Replies to CSV, JSON, or XLSX 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`. Reuse the follower export loop above. Change only the required target, tool type, and output names: ```kotlin theme={null} val targetTweetId = "1893704267862470862" val estimate: ExtractionEstimateCostResponse = client.extractions().estimateCost( ExtractionEstimateCostParams.builder() .toolType(ExtractionEstimateCostParams.ToolType.REPLY_EXTRACTOR) .targetTweetId(targetTweetId) .build() ) val job: ExtractionRunResponse = client.extractions().run( ExtractionRunParams.builder() .toolType(ExtractionRunParams.ToolType.REPLY_EXTRACTOR) .targetTweetId(targetTweetId) .build() ) val pageParams = ExtractionRetrieveParams.builder() .id(job.id()) .limit(1000L) if (!cursor.isNullOrEmpty()) { pageParams.cursor(cursor) } val page: ExtractionRetrieveResponse = client.extractions().retrieve(pageParams.build()) ``` Persist `job.id()` before polling so a queue retry can resume with `client.extractions().retrieve(job.id())` and repeat `client.extractions().exportResults(...)` after completion. Stream `page.results()` to `Paths.get("xquik-replies.jsonl")`. Keep `page.nextCursor()` as the checkpoint while streaming replies to JSON Lines. Pass it back as `cursor` on the next `ExtractionRetrieveParams.builder()` call. Keep the same `estimate.allowed()` credit branch from the follower export workflow before calling `run`. Export the completed job with the same helper used above: ```kotlin theme={null} client.extractions().exportResults( ExtractionExportResultsParams.builder() .id(job.id()) .format(ExtractionExportResultsParams.Format.CSV) .build() ).use { export -> Files.copy(export.body(), Paths.get("xquik-replies.csv"), StandardCopyOption.REPLACE_EXISTING) } ``` Repeat the export with `ExtractionExportResultsParams.Format.JSON` to write `Paths.get("xquik-replies.json")` and `ExtractionExportResultsParams.Format.XLSX` to write `Paths.get("xquik-replies.xlsx")`. 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. ## Workflow: Post Media Tweets and DM Attachments 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. ```kotlin theme={null} import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.x_twitter_scraper.api.client.XTwitterScraperClient import com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClient import com.x_twitter_scraper.api.core.http.HttpResponseFor import com.x_twitter_scraper.api.models.x.dm.DmSendParams import com.x_twitter_scraper.api.models.x.dm.DmSendResponse import com.x_twitter_scraper.api.models.x.media.MediaUploadParams import com.x_twitter_scraper.api.models.x.media.MediaUploadResponse import com.x_twitter_scraper.api.models.x.tweets.TweetCreateParams import com.x_twitter_scraper.api.models.x.tweets.TweetCreateResponse import java.nio.file.Paths val client: XTwitterScraperClient = XTwitterScraperOkHttpClient.fromEnv() val objectMapper = ObjectMapper() fun createTweetHandoff( payload: Map, base: Map, ): Map { val handoff = linkedMapOf() val billing = payload["billing"] as Map<*, *> val request = payload["request"] as Map<*, *> val result = payload["result"] as? Map<*, *> val terminal = payload["terminal"] == true handoff["status"] = payload["status"] handoff["terminal"] = terminal handoff["safe_to_retry"] = payload["safeToRetry"] handoff["write_action_id"] = payload["id"] handoff["request_hash"] = request["hash"] handoff["tweet_id"] = result?.get("id") ?: payload["tweetId"] handoff["charged"] = billing["charged"] handoff["charged_credits"] = billing["chargedCredits"] handoff["poll"] = if (terminal) null else payload["statusUrl"] handoff.putAll(base) return handoff } val tweetPayload: Map = client.x().tweets().withRawResponse().create( TweetCreateParams.builder() .account("@username") .text("Shipping the weekly X API video.") .addMedia("https://static.example.com/reports/x-api-export.mp4") .build() ).use { response: HttpResponseFor -> objectMapper.readValue( response.body(), object : TypeReference>() {}, ) } val tweetHandoff = createTweetHandoff( tweetPayload, linkedMapOf( "account" to "@username", "media" to listOf("https://static.example.com/reports/x-api-export.mp4"), ), ) val replyPayload: Map = client.x().tweets().withRawResponse().create( TweetCreateParams.builder() .account("@username") .text("Here is the chart behind the update.") .addMedia("https://static.example.com/reports/reply-chart.png") .replyToTweetId("1893704267862470862") .build() ).use { response: HttpResponseFor -> objectMapper.readValue( response.body(), object : TypeReference>() {}, ) } val replyHandoff = createTweetHandoff( replyPayload, linkedMapOf( "account" to "@username", "reply_to_tweet_id" to "1893704267862470862", "media" to listOf("https://static.example.com/reports/reply-chart.png"), ), ) val media: MediaUploadResponse = client.x().media().upload( MediaUploadParams.builder() .account("@username") .file(Paths.get("handoff.png")) .build() ) val dm: DmSendResponse = client.x().dm().send( "44196397", DmSendParams.builder() .account("@username") .text("Here is the requested asset.") .addMediaId(media.mediaId()) .build() ) val dmHandoff = linkedMapOf( "status" to "sent", "message_id" to dm.messageId(), "media_id" to media.mediaId(), "account" to "@username", "user_id" to "44196397", ) println(objectMapper.writeValueAsString(tweetHandoff)) println(objectMapper.writeValueAsString(replyHandoff)) println(objectMapper.writeValueAsString(dmHandoff)) ``` Use `client.x().tweets().withRawResponse().create(...)` to capture the durable write action. Send a unique `Idempotency-Key`, store `id`, `request.hash`, `billing`, `result`, and `statusUrl`, then poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. Keep DM body text in private systems. Shared logs and handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and lifecycle status. Leave `replyToMessageId` unset because the REST endpoint rejects DM reply threading. Use public media URLs with `client.x().tweets().create`. Do not pass uploaded `media.mediaId()` values to tweet creation. ## Error Handling The Kotlin SDK uses the same generated exception hierarchy as the Java SDK. Throws `BadRequestException`. Throws `UnauthorizedException`. Throws `PermissionDeniedException`. Throws `NotFoundException`. Throws `UnprocessableEntityException`. Throws `RateLimitException`. Throws `InternalServerException`. Connection and I/O failures use `XTwitterScraperIoException`. All SDK exceptions inherit from `XTwitterScraperException`. ## Pagination Paginated responses expose generated fields such as `hasNextPage`. Use endpoint cursor parameters from the API reference for follow-up requests. ```kotlin theme={null} if (tweets.hasNextPage()) { System.err.println("More results are available") } ``` ## Webhooks & References * [Search Tweets](/api-reference/x/search-tweets) * [Create Tweet](/api-reference/x-write/create-tweet) * [Get Write Action Status](/api-reference/x-write/get-write-action-status) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Create Monitor](/api-reference/monitors/create) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [REST API Overview](/api-reference/overview) * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-kotlin) # PHP SDK for Tweet Search, Exports & X Automation Source: https://docs.xquik.com/sdks/php Use Xquik's PHP SDK to search tweets, export replies to JSON Lines, CSV, or XLSX, post media tweets, upload media, send DMs, and run durable X API workers.
For the complete documentation index, see llms.txt.
Use PHP 8.1+ named parameters and typed exceptions in Laravel or Symfony workers. Search tweets, export followers or replies, post media, upload files, and send DMs. | PHP task | SDK call | Save | | ---------------- | ------------------------------ | ------------------- | | Search tweets | `$client->x->tweets->search()` | `$page->nextCursor` | | Export followers | `$client->extractions->run()` | `$job->id` | ## Install ```bash theme={null} composer require xquik/x-twitter-scraper ``` If your project needs the GitHub source directly, add the repository in `composer.json` as described in the [source repository](https://github.com/Xquik-dev/x-twitter-scraper-php). ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` ## Basic Example Search tweets and write durable JSON Lines handoff rows: ```php theme={null} x->tweets->search( q: 'from:username webhook OR SDK', limit: 10, ); foreach ($page->tweets as $tweet) { $row = [ 'tweet_id' => $tweet->id, 'text' => $tweet->text, 'author_username' => $tweet->author?->username, 'created_at' => $tweet->createdAt, ]; echo json_encode($row, JSON_THROW_ON_ERROR) . PHP_EOL; } ``` ## Workflow: Search Tweets to JSON Lines, CSV, or XLSX Use this workflow when a PHP worker, Laravel command, Symfony console command, or cron job needs tweet search results in a durable file for a queue, warehouse loader, analyst CSV export, XLSX workbook, or CRM enrichment step. `$client->x->tweets->search()` calls `GET /x/tweets/search`. The generated parameter model is `TweetSearchParams`, and the service method exposes the same request controls as named arguments: `q`, `limit`, `cursor`, `sinceTime`, `untilTime`, and `queryType`. ```php theme={null} x->tweets->search( q: $query, cursor: $cursor, queryType: QueryType::LATEST, ); foreach ($page->tweets as $tweet) { /** @var SearchTweet $tweet */ $row = [ 'source' => 'xquik.php.search', 'query' => $query, 'tweet_id' => $tweet->id, 'text' => $tweet->text, 'author_id' => $tweet->author?->id, 'author_username' => $tweet->author?->username, 'author_name' => $tweet->author?->name, 'created_at' => $tweet->createdAt, '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, 'page_index' => $pageIndex, 'page_cursor' => $pageCursor, 'next_cursor' => '' === $page->nextCursor ? null : $page->nextCursor, 'has_next_page' => $page->hasNextPage, ]; $csvRow = []; foreach ($headers as $header) { $csvRow[] = $row[$header]; } fputcsv($csvHandle, $csvRow); fwrite( $jsonlHandle, json_encode($row, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL ); } $cursor = $page->hasNextPage ? $page->nextCursor : null; $pageIndex++; } while (null !== $cursor && '' !== $cursor); } finally { fclose($jsonlHandle); fclose($csvHandle); } ``` ### Request Mapping PHP argument `q` maps to REST `q`. Use it for an X search query such as `from:username`, a keyword, hashtag, or boolean operator query. PHP argument `limit` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `$page->hasNextPage` is true, keep the same `q`, filters, `queryType`, and `limit` when you continue with `$page->nextCursor`. PHP argument `cursor` maps to REST `cursor`. Pass the opaque cursor from `$page->nextCursor` to request the next page. PHP argument `sinceTime` maps to REST `sinceTime`. Use the inclusive ISO bound on every page. PHP argument `untilTime` maps to REST `untilTime`. Use the exclusive ISO bound on every page. PHP argument `queryType` maps to REST `queryType`. Use `QueryType::LATEST` for chronological search or `QueryType::TOP` for engagement-ranked search. ### Returned Data & Handoff `$client->x->tweets->search()` returns `PaginatedTweets`. Use `$page->tweets` for the tweet array, `$page->hasNextPage` to decide whether another page exists, and `$page->nextCursor` as the checkpoint for the next request. For bounded pulls that return fewer tweets than `limit`, pass `$page->nextCursor` back as `cursor` with the same query, filters, `queryType`, and `limit`. Each `SearchTweet` includes typed properties such as `$id`, `$text`, `$author`, `$createdAt`, `$likeCount`, `$replyCount`, `$retweetCount`, `$quoteCount`, `$bookmarkCount`, `$viewCount`, 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 retries connection errors, timeouts, 408, 409, 429, and 5xx responses 2 times by default. For explicit `limit` pulls, resume with the same query, filters, `queryType`, and `limit`; only `cursor` changes. ## Workflow: Follower Export to CSV, JSON, or XLSX Use this workflow when a PHP worker, Laravel command, Symfony console command, or cron job needs an owned follower list for CRM import, warehouse loading, account scoring, analyst CSV, XLSX 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`. `$client->extractions->run()` returns the queued `202 Accepted` receipt from `POST /extractions`: REST `id`, `toolType`, and `status: "running"` as PHP `$job->id`, `$job->toolType`, and `$job->status`. Store `$job->id` immediately, then poll `$client->extractions->retrieve()` before reading pages or calling `$client->extractions->exportResults()`. Credit reservation happens after the job starts. If available credits changed since `estimateCost`, the run can fetch only the affordable count before export or mark the job `failed` with `insufficient_credits`. ```php theme={null} extractions->estimateCost( toolType: EstimateToolType::FOLLOWER_EXPLORER, targetUsername: $targetUsername, ); if (!$estimate->allowed) { throw new RuntimeException("Follower export requires {$estimate->creditsRequired} credits."); } $job = $client->extractions->run( toolType: RunToolType::FOLLOWER_EXPLORER, targetUsername: $targetUsername, ); $completed = false; for ($attempt = 0; $attempt < 120; $attempt++) { $statusPage = $client->extractions->retrieve($job->id, limit: 1); $status = $statusPage->job['status'] ?? null; if ('completed' === $status) { $completed = true; break; } if ('failed' === $status) { throw new RuntimeException('Follower export failed.'); } sleep(10); } if (!$completed) { throw new RuntimeException('Follower export did not complete before timeout.'); } $cursor = null; $handle = fopen('xquik-followers.jsonl', 'wb'); if (false === $handle) { throw new RuntimeException('Could not open xquik-followers.jsonl'); } try { do { $page = $client->extractions->retrieve($job->id, cursor: $cursor, limit: 1000); foreach ($page->results as $user) { fwrite( $handle, json_encode($user, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL ); } $cursor = $page->hasMore ? $page->nextCursor : null; } while (null !== $cursor && '' !== $cursor); } finally { fclose($handle); } file_put_contents( 'xquik-followers.csv', $client->extractions->exportResults($job->id, format: ExportFormat::CSV), ); file_put_contents( 'xquik-followers.json', $client->extractions->exportResults($job->id, format: ExportFormat::JSON), ); file_put_contents( 'xquik-followers.xlsx', $client->extractions->exportResults($job->id, format: ExportFormat::XLSX), ); ``` Persist `$job->id`, `$targetUsername`, `$estimate->estimatedResults`, and `$estimate->source` before polling so another queue worker can resume without rerunning the extraction. `$client->extractions->retrieve()` returns `$page->results`, `$page->hasMore`, and `$page->nextCursor`; pass `$page->nextCursor` back as `cursor` 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. ## Workflow: Tweet Replies to CSV, JSON, or XLSX Use this workflow when a PHP worker needs every reply on a public tweet in a durable file for moderation review, customer support triage, analyst export, or a warehouse loader. `$client->extractions->estimateCost()` and `$client->extractions->run()` map to the extraction workflow. For tweet replies, use `ToolType::REPLY_EXTRACTOR`; `reply_extractor` requires `targetTweetID`. `$client->extractions->retrieve()` returns `results`, `hasMore`, and `nextCursor` for pagination. `$client->extractions->exportResults()` returns the completed job as a string and supports `CSV`, `JSON`, and `XLSX` export formats. Reuse the polling, JSON Lines pagination, and export structure from the follower workflow. Only the tool type, target field, and filenames change: ```php theme={null} $targetTweetID = '1893704267862470862'; $estimate = $client->extractions->estimateCost( toolType: EstimateToolType::REPLY_EXTRACTOR, targetTweetID: $targetTweetID, ); if (!$estimate->allowed) { throw new RuntimeException('Insufficient credits for reply extraction.'); } $job = $client->extractions->run( toolType: RunToolType::REPLY_EXTRACTOR, targetTweetID: $targetTweetID, ); // Run the same polling loop and JSONL pagination loop from the follower workflow. // Set the JSON Lines destination to fopen('xquik-replies.jsonl', 'wb'). file_put_contents( 'xquik-replies.csv', $client->extractions->exportResults($job->id, format: ExportFormat::CSV), ); file_put_contents( 'xquik-replies.json', $client->extractions->exportResults($job->id, format: ExportFormat::JSON), ); file_put_contents( 'xquik-replies.xlsx', $client->extractions->exportResults($job->id, format: ExportFormat::XLSX), ); ``` Cost: 1 credit per reply extracted or returned. Store `$job->id` on the queue job, ticket, or warehouse batch before polling so another worker can resume with `$client->extractions->retrieve()`. Keep `$page->nextCursor` as the checkpoint and pass it back as `cursor` 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. ## Workflow: Post Media Tweets and DM Attachments Use this workflow when a PHP app needs to publish a media tweet, reply with media, or send a direct message with an uploaded local file. `POST /x/tweets` accepts public media URLs through `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 its `$media->mediaID` only for the one-item `mediaIDs` array on `$client->x->dm->send()`. Use `$client->x->tweets->raw->create()` to capture the durable write action. Send a unique `Idempotency-Key`, store `id`, `request.hash`, `billing`, `result`, and `statusUrl`, then poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. ```php theme={null} $payload * * @return array */ function createTweetHandoff(Client $client, array $payload): array { $response = $client->x->tweets->raw->create(params: $payload); $action = json_decode( (string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR, ); $result = $action['result'] ?? []; return [ 'status' => $action['status'], 'terminal' => $action['terminal'], 'safe_to_retry' => $action['safeToRetry'], 'write_action_id' => $action['id'], 'request_hash' => $action['request']['hash'], 'tweet_id' => $result['id'] ?? $action['tweetId'] ?? null, 'charged' => $action['billing']['charged'], 'charged_credits' => $action['billing']['chargedCredits'], 'poll' => $action['terminal'] ? null : $action['statusUrl'], ]; } $client = new Client( apiKey: getenv('X_TWITTER_SCRAPER_API_KEY') ?: '' ); $tweetHandoff = createTweetHandoff($client, [ 'account' => '@username', 'text' => 'Shipping the weekly X API video.', 'media' => ['https://static.example.com/reports/x-api-export.mp4'], ]); $replyHandoff = createTweetHandoff($client, [ 'account' => '@username', 'text' => 'Here is the chart behind the update.', 'media' => ['https://static.example.com/reports/reply-chart.png'], 'replyToTweetID' => $tweetHandoff['tweet_id'] ?? '1893704267862470862', ]); $localFile = fopen('handoff.png', 'rb'); if (false === $localFile) { throw new RuntimeException('Could not open handoff.png'); } try { $media = $client->x->media->upload( account: '@username', file: FileParam::fromResource($localFile), ); } finally { fclose($localFile); } $dm = $client->x->dm->send( '44196397', account: '@username', text: 'Here is the requested asset.', mediaIDs: [$media->mediaID], ); $dmHandoff = [ 'message_id' => $dm->messageID, 'media_id' => $media->mediaID, 'user_id' => '44196397', 'account' => '@username', 'status' => 'sent', ]; fwrite( STDOUT, json_encode( [ 'tweet_handoff' => $tweetHandoff, 'reply_handoff' => $replyHandoff, 'dm_handoff' => $dmHandoff, ], JSON_THROW_ON_ERROR ) . PHP_EOL ); ``` Store `write_action_id` and `charged_credits`, then poll [Get Write Action Status](/api-reference/x-write/get-write-action-status) before retrying a pending tweet or reply. Store `tweet_id` on the CMS, queue job, or agent state after confirmed writes. Store `$dm->messageID`, `$media->mediaID`, `account`, `user_id`, and send status on the support ticket or CRM note. Keep DM body text in private systems. Shared logs, public artifacts, queue status, and agent handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and send status instead of full DM bodies. Leave generated `replyToMessageID` unset even if SDK params expose it; the REST endpoint rejects DM reply threading. Text-only tweet and reply writes cost 30 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 `media` with public media URLs. ## Error Handling The SDK throws subclasses of `XTwitterScraper\Core\Exceptions\APIException`. Throws `BadRequestException`. Throws `AuthenticationException`. Throws `PermissionDeniedException`. Throws `NotFoundException`. Throws `UnprocessableEntityException`. Throws `RateLimitException`. Throws `InternalServerException`. ```php theme={null} account->retrieve(); } catch (APIConnectionException $e) { fwrite(STDERR, "Connection failed\n"); } catch (APIStatusException $e) { fwrite(STDERR, $e->getMessage() . PHP_EOL); } ``` ## Pagination Paginated responses expose fields such as `hasNextPage`. Pass the endpoint's cursor fields when requesting more pages. ```php theme={null} x->tweets->search(q: 'xquik', limit: 20); if ($page->hasNextPage) { fwrite(STDERR, "More results are available\n"); } ``` ## Webhooks & References * [Search Tweets](/api-reference/x/search-tweets) * [Create Tweet](/api-reference/x-write/create-tweet) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Create Monitor](/api-reference/monitors/create) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [REST API Overview](/api-reference/overview) * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-php) # Python SDK for Tweet Search, Exports & X Automation Source: https://docs.xquik.com/sdks/python Use the Xquik Python SDK to search tweets, export CSV, JSON Lines, or XLSX, post tweets, upload media, send DMs, and build X API workers. See code examples.
For the complete documentation index, see llms.txt.
## Distinct Python Fit Choose Python for typed synchronous or asynchronous clients. It fits scripts, notebooks, workers, pandas pipelines, and agent tools. Use TypeScript when the runtime and downstream models are Node.js-based. Use the Python SDK for typed sync or async access to Xquik from scripts, notebooks, workers, agent tools, and backend services. Use this page when Python must search tweets or export them to CSV, JSON Lines, or XLSX. It can export followers, post tweets, upload media, send DMs, monitor tweets, or load records into pandas, a warehouse, a queue, CRM, or an agent. | Python task | SDK call | Save | | ---------------- | ------------------------ | ------------- | | Search tweets | `client.x.tweets.search` | `next_cursor` | | Export followers | `client.extractions.run` | `job.id` | ## Install ```bash theme={null} pip install x_twitter_scraper ``` ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` ```python theme={null} import os from x_twitter_scraper import XTwitterScraper client = XTwitterScraper( api_key=os.environ.get("X_TWITTER_SCRAPER_API_KEY"), ) ``` ## Basic Example Search tweets and write durable JSON Lines handoff rows: ```python theme={null} import json import sys from x_twitter_scraper import XTwitterScraper client = XTwitterScraper() page = client.x.tweets.search( q="from:username webhook OR SDK", limit=10, ) for tweet in page.tweets: row = { "tweet_id": tweet.id, "text": tweet.text, "author_username": tweet.author.username if tweet.author else None, "created_at": tweet.created_at, } sys.stdout.write(json.dumps(row, ensure_ascii=False) + "\n") ``` Async clients use the same generated method names: ```python theme={null} import asyncio import json import sys from x_twitter_scraper import AsyncXTwitterScraper client = AsyncXTwitterScraper() async def main() -> None: page = await client.x.tweets.search(q="xquik", limit=10) for tweet in page.tweets: row = { "tweet_id": tweet.id, "text": tweet.text, "author_username": tweet.author.username if tweet.author else None, "created_at": tweet.created_at, } sys.stdout.write(json.dumps(row, ensure_ascii=False) + "\n") asyncio.run(main()) ``` ## Workflow: Search Tweets to CSV, JSON Lines, or XLSX This job is for data scripts, notebooks, scheduled workers, and agent tools that need tweet search results in a durable handoff file. It calls `GET /x/tweets/search` through `client.x.tweets.search`, uses the generated `TweetSearchParams` shape, and writes analyst-friendly CSV plus JSON Lines for queues, warehouses, and replayable processing. ```python theme={null} import csv import json from pathlib import Path from x_twitter_scraper import XTwitterScraper client = XTwitterScraper() query = "from:username webhook OR SDK" csv_output = Path("xquik-tweet-search.csv") jsonl_output = Path("xquik-tweet-search.jsonl") cursor = None page_index = 0 with csv_output.open("w", newline="", encoding="utf-8") as csv_file, jsonl_output.open( "w", encoding="utf-8", ) as jsonl_file: writer = csv.DictWriter( csv_file, fieldnames=[ "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", ], ) writer.writeheader() while True: page_cursor = cursor page = client.x.tweets.search( q=query, query_type="Latest", cursor=cursor, ) for tweet in page.tweets: row = { "source": "xquik.python.search", "query": query, "tweet_id": tweet.id, "text": tweet.text, "author_id": tweet.author.id if tweet.author else None, "author_username": tweet.author.username if tweet.author else None, "author_name": tweet.author.name if tweet.author else None, "created_at": tweet.created_at, "like_count": tweet.like_count or 0, "reply_count": tweet.reply_count or 0, "retweet_count": tweet.retweet_count or 0, "quote_count": tweet.quote_count or 0, "view_count": tweet.view_count or 0, "bookmark_count": tweet.bookmark_count or 0, "is_note_tweet": tweet.is_note_tweet or False, "page_index": page_index, "page_cursor": page_cursor, "next_cursor": page.next_cursor or None, "has_next_page": page.has_next_page, } writer.writerow(row) jsonl_file.write(json.dumps(row, ensure_ascii=False) + "\n") if not page.has_next_page or not page.next_cursor: break cursor = page.next_cursor page_index += 1 ``` The method accepts the same query inputs as the REST endpoint: Python argument `q` maps to REST `q`. Use it for the required X search query with keywords, handles, hashtags, or operators. Python argument `limit` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.has_next_page` is true, keep the same `q`, filters, `query_type`, and `limit` when you continue with `page.next_cursor`. Python argument `cursor` maps to REST `cursor`. Pass the opaque cursor from `page.next_cursor` to request the next page. Python argument `since_time` maps to REST `sinceTime`. Use it as the ISO 8601 lower time bound. Python argument `until_time` maps to REST `untilTime`. Use it as the ISO 8601 upper time bound. Python argument `query_type` maps to REST `queryType`. Use `Latest` for chronological results or `Top` for engagement-ranked results. ## Returned Data & Handoff `client.x.tweets.search` returns a `PaginatedTweets` Pydantic model: JSON field `tweets`. Contains `SearchTweet` records with `id`, `text`, optional `author`, `created_at`, `like_count`, `reply_count`, `retweet_count`, `quote_count`, `bookmark_count`, `view_count`, and `is_note_tweet` when available. Python field `page.has_next_page`. JSON field `has_next_page`. Tells your script whether another page exists. JSON field `next_cursor`. Store it only when `page.has_next_page` is true. For bounded pulls that return fewer tweets than `limit`, pass it back as `cursor` with the same query, filters, `query_type`, and `limit`. Project `page.tweets` into CSV rows for analysts and JSON Lines rows for queues and data lakes in `xquik-tweet-search.jsonl`. Load the same projected rows into pandas or openpyxl when account teams need an XLSX workbook. Store `tweet_id`, `author_username`, engagement counts, `page_index`, `page_cursor`, `next_cursor`, and `has_next_page` so a script can resume from the last saved cursor without replaying raw SDK models. For explicit `limit` pulls, resume with the same query, filters, `query_type`, and `limit`; only `cursor` changes. ## Workflow: Follower Export to CSV, JSON, or XLSX Use this workflow when a Python script, notebook, worker, or agent tool needs an owned follower list for a CRM import, warehouse load, analyst CSV file, XLSX workbook, or resumable JSON handoff. It calls `POST /extractions/estimate` through `client.extractions.estimate_cost`, creates the job with `client.extractions.run`, reads saved rows with `client.extractions.retrieve`, and downloads files with `client.extractions.export_results`. `client.extractions.run` returns the queued `202 Accepted` receipt from `POST /extractions`: REST `id`, `toolType`, and `status: "running"` as Python `job.id`, `job.tool_type`, and `job.status`. Store `job.id` immediately, then poll `client.extractions.retrieve` before reading pages or calling `client.extractions.export_results`. Credit reservation happens after the job starts. If available credits changed since `estimate_cost`, the run can fetch only the affordable count before export or mark the job `failed` with `insufficient_credits`. ```python theme={null} import json import time from pathlib import Path from x_twitter_scraper import XTwitterScraper client = XTwitterScraper() target_username = "username" estimate = client.extractions.estimate_cost( tool_type="follower_explorer", target_username=target_username, ) if not estimate.allowed: raise RuntimeError("Insufficient credits for follower export.") job = client.extractions.run( tool_type="follower_explorer", target_username=target_username, ) while True: status_page = client.extractions.retrieve(job.id, limit=1) status = status_page.job.get("status") if status == "completed": break if status == "failed": raise RuntimeError("Follower export failed.") time.sleep(10) cursor = None with Path("xquik-followers.jsonl").open("w", encoding="utf-8") as jsonl_file: while True: page = client.extractions.retrieve(job.id, limit=1000, cursor=cursor) for row in page.results: jsonl_file.write(json.dumps(row, ensure_ascii=False) + "\n") if not page.has_more or not page.next_cursor: break cursor = page.next_cursor client.extractions.export_results(job.id, format="csv").write_to_file( Path("xquik-followers.csv"), ) client.extractions.export_results(job.id, format="json").write_to_file( Path("xquik-followers.json"), ) client.extractions.export_results(job.id, format="xlsx").write_to_file( Path("xquik-followers.xlsx"), ) ``` `follower_explorer` requires `target_username`. Persist `job.id`, `target_username`, `estimate.estimated_results`, and `estimate.source` before polling so a notebook restart, queue retry, or worker restart can resume the same follower export. `client.extractions.retrieve` returns `results`, `has_more`, and `next_cursor`; pass `next_cursor` back as `cursor` when you need stored JSON pages before exporting files. 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. Map exported `User ID` or row `xUserId` as the CRM unique key. Cost: 1 credit per follower extracted or returned. Exports are free after the extraction job exists. ## Workflow: Tweet Replies to CSV, JSON, or XLSX Use this workflow when a Python script, notebook, worker, or agent tool needs every reply under one tweet as a saved extraction, JSON Lines handoff, or CSV/JSON/XLSX file export. It uses `client.extractions.estimate_cost`, `run`, `retrieve`, and `export_results`. Reuse the polling, row pagination, and export structure from the follower workflow. Only the tool type, target field, and output filenames change: ```python theme={null} target_tweet_id = "1893704267862470862" estimate = client.extractions.estimate_cost( tool_type="reply_extractor", target_tweet_id=target_tweet_id, ) if not estimate.allowed: raise RuntimeError("Insufficient credits for reply extraction.") job = client.extractions.run( tool_type="reply_extractor", target_tweet_id=target_tweet_id, ) # Run the same polling and JSONL pagination loops from the follower workflow. # Set the JSON Lines destination to Path("xquik-replies.jsonl"). client.extractions.export_results(job.id, format="csv").write_to_file( Path("xquik-replies.csv"), ) client.extractions.export_results(job.id, format="json").write_to_file( Path("xquik-replies.json"), ) client.extractions.export_results(job.id, format="xlsx").write_to_file( Path("xquik-replies.xlsx"), ) ``` `reply_extractor` requires `target_tweet_id`. `client.extractions.retrieve` returns `results`, `has_more`, and `next_cursor`; the shared pagination loop passes `next_cursor` back as `cursor`. 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. `client.extractions.export_results` supports `csv`, `json`, and `xlsx` for file handoff. Cost: 1 credit per reply extracted or returned. ## Workflow: Post Media Tweets and DM Attachments Use this workflow when a Python worker, notebook, support queue, or agent needs to post a media-backed tweet, reply with media, or send one uploaded media item in a DM. Tweet and reply media posts use public media URLs directly on `client.x.tweets.create`. Send up to 4 image URLs or exactly 1 MP4 video URL up to 100 MB. Do not mix video with other media. Do not upload first when the media URL is already public. ```python theme={null} import json import sys from typing import Any from x_twitter_scraper import XTwitterScraper client = XTwitterScraper() def create_tweet_handoff( action: dict[str, Any], base: dict[str, Any], ) -> dict[str, Any]: result = action.get("result") or {} return { "status": action["status"], "terminal": action["terminal"], "safe_to_retry": action["safeToRetry"], "write_action_id": action["id"], "request_hash": action["request"]["hash"], "tweet_id": result.get("id") or action.get("tweetId"), "charged": action["billing"]["charged"], "charged_credits": action["billing"]["chargedCredits"], "poll": None if action["terminal"] else action["statusUrl"], **base, } ``` Use `with_raw_response.create` to capture the durable write action. Send a unique `Idempotency-Key`, store `id`, `request.hash`, `billing`, `result`, and `statusUrl`, then poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. ```python theme={null} response = client.x.tweets.with_raw_response.create( account="@username", text="New demo video is live.", media=["https://example.com/product-demo.mp4"], ) payload = response.json() tweet_handoff = create_tweet_handoff( payload, { "account": "@username", "media": ["https://example.com/product-demo.mp4"], }, ) sys.stdout.write(json.dumps(tweet_handoff, ensure_ascii=False) + "\n") ``` To post an image reply, add `reply_to_tweet_id`: ```python theme={null} response = client.x.tweets.with_raw_response.create( account="@username", text="Here is the requested screenshot.", reply_to_tweet_id="1893704267862470862", media=["https://example.com/export-preview.png"], ) payload = response.json() reply_handoff = create_tweet_handoff( payload, { "account": "@username", "reply_to_tweet_id": "1893704267862470862", "media": ["https://example.com/export-preview.png"], }, ) sys.stdout.write(json.dumps(reply_handoff, ensure_ascii=False) + "\n") ``` For DM attachments, upload the local file first and pass the returned `media.media_id` as the only `media_ids` item: ```python theme={null} import json import sys from pathlib import Path media = client.x.media.upload( account="@username", file=Path("./handoff.png"), ) dm = client.x.dm.send( user_id="44196397", account="@username", text="Here is the asset.", media_ids=[media.media_id], ) dm_handoff = { "status": "sent", "message_id": dm.message_id, "media_id": media.media_id, "account": "@username", "user_id": "44196397", } sys.stdout.write(json.dumps(dm_handoff, ensure_ascii=False) + "\n") ``` `client.x.tweets.create` returns `tweet.tweet_id` for confirmed posts. Raw create responses can also include the pending write fields above when confirmation is still running. `client.x.media.upload` returns `media.media_id` for DM attachments, and `client.x.dm.send` returns `dm.message_id` for support tickets, CRM records, queue jobs, or agent memory. Keep DM body text in private systems. Shared logs, public artifacts, queue status, and agent handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and send status instead of full DM bodies. Leave `reply_to_message_id` unset even if generated SDK types expose it; the REST endpoint rejects DM reply threading. Text-only tweet and reply writes cost 30 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.media_id` values to `client.x.tweets.create`; that method uses `media` with public media URLs. ## Cost, Limits & Retries 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`. Read calls are rate-limited, and `429` responses include `Retry-After`. The client retries connection errors, 408, 409, 429, and 5xx responses by default. Handle `RateLimitError` with backoff, and fix 400, 401, 403, 404, or 422 responses before retrying. ## Error Handling All SDK exceptions inherit from `x_twitter_scraper.APIError`. Throws `BadRequestError`. Throws `AuthenticationError`. Throws `PermissionDeniedError`. Throws `NotFoundError`. Throws `UnprocessableEntityError`. Throws `RateLimitError`. Throws `InternalServerError`. ```python theme={null} import x_twitter_scraper try: account = client.account.retrieve() except x_twitter_scraper.APIConnectionError as exc: print(f"Connection failed: {exc.__cause__}") except x_twitter_scraper.APIStatusError as exc: print(f"HTTP {exc.status_code}") ``` The client retries connection errors, 408, 409, 429, and 5xx responses by default. Pass `max_retries` when constructing the client to change retry behavior. ## Pagination List endpoints return page models with `has_next_page` and cursor fields. ```python theme={null} page = client.x.tweets.search(q="xquik", limit=20) if page.has_next_page: print("More results are available") ``` ## Webhooks & References * [Search Tweets](/api-reference/x/search-tweets) * [Create Extraction](/api-reference/extractions/create) * [Get Extraction](/api-reference/extractions/twitter-extraction-results) * [Export Extraction](/api-reference/extractions/export) * [Create Tweet](/api-reference/x-write/create-tweet) * [Get Write Action Status](/api-reference/x-write/get-write-action-status) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [Twitter Scraper API Overview](/api-reference/overview) * Download the OpenAPI schema: `curl -o openapi.json https://xquik.com/openapi.json` * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-python) # Ruby SDK for Tweet Search, Exports & X Automation Source: https://docs.xquik.com/sdks/ruby Use the Xquik Ruby SDK to search tweets, export CSV, JSON Lines, or XLSX, post tweets, upload media, send DMs, and run durable Rails or Sidekiq X API jobs.
For the complete documentation index, see llms.txt.
Use the Ruby SDK for Ruby 3.2+ applications that need typed REST access, retries, Yard docs, RBS, RBI, and connection pooling. Use this page when a Ruby app, Rails job, or Sidekiq worker must search tweets or export them to CSV, JSON Lines, or XLSX. It can export followers, upload media, send DMs, monitor tweets, or load records into a warehouse, CRM, queue, or agent. | Ruby task | SDK call | Save | | ---------------- | ------------------------ | ------------- | | Search tweets | `client.x.tweets.search` | `next_cursor` | | Export followers | `client.extractions.run` | `job.id` | ## Install ```bash theme={null} gem install x-twitter-scraper ``` Or add it to your Gemfile: ```ruby theme={null} gem "x-twitter-scraper", "~> 0.9.1" ``` ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` ## Basic Example Search tweets and write durable JSON Lines handoff rows: ```ruby theme={null} require "json" require "x_twitter_scraper" client = XTwitterScraper::Client.new( api_key: ENV["X_TWITTER_SCRAPER_API_KEY"] ) page = client.x.tweets.search( q: "from:username webhook OR SDK", limit: 10 ) page.tweets.each do |tweet| row = { tweet_id: tweet.id, text: tweet.text, author_username: tweet.author&.username, created_at: tweet.created_at } puts(JSON.generate(row)) end ``` ## Workflow: Search Tweets to CSV, JSON Lines, or XLSX This job is for Ruby workers, Rails jobs, Sidekiq queues, and agent tools that need tweet search results in durable handoff files. It calls `GET /x/tweets/search` through `client.x.tweets.search`, uses the generated `XTwitterScraper::X::TweetSearchParams` shape, and writes analyst-friendly CSV plus JSON Lines for queues, warehouses, and replayable processing. ```ruby theme={null} require "csv" require "json" require "x_twitter_scraper" client = XTwitterScraper::Client.new( api_key: ENV["X_TWITTER_SCRAPER_API_KEY"] ) query = "from:username webhook OR SDK" cursor = nil page_index = 0 headers = [ "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" ] CSV.open("xquik-tweet-search.csv", "w", write_headers: true, headers: headers) do |csv| File.open("xquik-tweet-search.jsonl", "w") do |jsonl| loop do page_cursor = cursor page = client.x.tweets.search( q: query, query_type: :Latest, cursor: cursor ) page.tweets.each do |tweet| row = { "source" => "xquik.ruby.search", "query" => query, "tweet_id" => tweet.id, "text" => tweet.text, "author_id" => tweet.author&.id, "author_username" => tweet.author&.username, "author_name" => tweet.author&.name, "created_at" => tweet.created_at, "like_count" => tweet.like_count || 0, "reply_count" => tweet.reply_count || 0, "retweet_count" => tweet.retweet_count || 0, "quote_count" => tweet.quote_count || 0, "view_count" => tweet.view_count || 0, "bookmark_count" => tweet.bookmark_count || 0, "is_note_tweet" => tweet.is_note_tweet || false, "page_index" => page_index, "page_cursor" => page_cursor, "next_cursor" => page.next_cursor == "" ? nil : page.next_cursor, "has_next_page" => page.has_next_page } csv << headers.map { |header| row.fetch(header) } jsonl.puts(JSON.generate(row)) end break unless page.has_next_page && page.next_cursor != "" cursor = page.next_cursor page_index += 1 end end end ``` The generated params map directly to the REST endpoint: Ruby keyword `q` maps to REST `q`. Use it for the required X search query with keywords, handles, hashtags, or operators. Ruby keyword `limit` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.has_next_page` is true, keep the same `q`, filters, `query_type`, and `limit` when you continue with `page.next_cursor`. Ruby keyword `cursor` maps to REST `cursor`. Pass the opaque cursor from `page.next_cursor` to request the next page. Ruby keyword `since_time` maps to REST `sinceTime`. Use it as the ISO 8601 lower time bound. Ruby keyword `until_time` maps to REST `untilTime`. Use it as the ISO 8601 upper time bound. Ruby keyword `query_type` maps to REST `queryType`. Use `:Latest` for chronological results or `:Top` for engagement-ranked results. ## Returned Data & Handoff `client.x.tweets.search` returns `XTwitterScraper::PaginatedTweets`: JSON field `tweets`. Contains `SearchTweet` records with `id`, `text`, optional `author`, `created_at`, `like_count`, `reply_count`, `retweet_count`, `quote_count`, `bookmark_count`, `view_count`, and `is_note_tweet` when available. Ruby field `page.has_next_page`. JSON field `has_next_page`. Tells your worker whether another page exists. JSON field `next_cursor`. Store it only when `page.has_next_page` is true. For bounded pulls that return fewer tweets than `limit`, pass it back as `cursor` with the same query, filters, `query_type`, and `limit`. Project `page.tweets` into CSV rows for analysts and JSON Lines rows for queues and data lakes. Store `tweet_id`, `author_username`, engagement counts, `page_index`, `page_cursor`, `next_cursor`, and `has_next_page` in `xquik-tweet-search.jsonl` so workers can resume safely or load the same records into XLSX, CRM, warehouse, or agent workflows. For explicit `limit` pulls, resume with the same query, filters, `query_type`, and `limit`; only `cursor` changes. ## Workflow: Follower Export to CSV, JSON, or XLSX Use this workflow when a Ruby app, Rails job, Sidekiq worker, or agent tool needs an owned follower list for a CRM import, warehouse load, analyst CSV file, XLSX workbook, or resumable JSON handoff. It calls `POST /extractions/estimate` through `client.extractions.estimate_cost`, creates the job with `client.extractions.run`, reads saved rows with `client.extractions.retrieve`, and downloads files with `client.extractions.export_results`. `client.extractions.run` returns the queued `202 Accepted` receipt from `POST /extractions`: REST `id`, `toolType`, and `status: "running"` as Ruby `job.id`, `job.tool_type`, and `job.status`. Store `job.id` immediately, then poll `client.extractions.retrieve` before reading pages or calling `client.extractions.export_results`. Credit reservation happens after the job starts. If available credits changed since `estimate_cost`, the run can fetch only the affordable count before export or mark the job `failed` with `insufficient_credits`. ```ruby theme={null} require "json" require "x_twitter_scraper" client = XTwitterScraper::Client.new( api_key: ENV["X_TWITTER_SCRAPER_API_KEY"] ) target_username = "username" estimate = client.extractions.estimate_cost( tool_type: :follower_explorer, target_username: target_username ) raise "Insufficient credits for follower export." unless estimate.allowed job = client.extractions.run( tool_type: :follower_explorer, target_username: target_username ) loop do status_page = client.extractions.retrieve(job.id, limit: 1) status = status_page.job[:status] || status_page.job["status"] break if status == "completed" raise "Follower export failed." if status == "failed" sleep 10 end cursor = nil File.open("xquik-followers.jsonl", "w") do |jsonl| loop do page = client.extractions.retrieve(job.id, limit: 1000, cursor: cursor) page.results.each do |row| jsonl.puts(JSON.generate(row)) end break unless page.has_more && page.next_cursor.to_s != "" cursor = page.next_cursor end end csv_response = client.extractions.export_results(job.id, format_: :csv) csv_response.rewind File.binwrite("xquik-followers.csv", csv_response.read) json_response = client.extractions.export_results(job.id, format_: :json) json_response.rewind File.binwrite("xquik-followers.json", json_response.read) xlsx_response = client.extractions.export_results(job.id, format_: :xlsx) xlsx_response.rewind File.binwrite("xquik-followers.xlsx", xlsx_response.read) ``` `follower_explorer` requires `target_username`. Persist `job.id`, `target_username`, `estimate.estimated_results`, and `estimate.source` before polling so a Sidekiq retry, Rails job retry, or worker restart can resume the same follower export. `client.extractions.retrieve` returns `results`, `has_more`, and `next_cursor`; pass `next_cursor` back as `cursor` when you need stored JSON pages before exporting files. 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. ## Workflow: Tweet Replies to CSV, JSON, or XLSX Use this workflow when a Ruby app, Rails job, Sidekiq worker, or agent tool needs every reply under one tweet as a saved extraction, JSON Lines handoff, or CSV/JSON/XLSX file export. Reuse the polling, JSON Lines pagination, and export structure from the follower workflow. Only the tool type, target field, and filenames change: ```ruby theme={null} target_tweet_id = "1893704267862470862" estimate = client.extractions.estimate_cost( tool_type: :reply_extractor, target_tweet_id: target_tweet_id ) raise "Insufficient credits for reply extraction." unless estimate.allowed job = client.extractions.run( tool_type: :reply_extractor, target_tweet_id: target_tweet_id ) # Run the same polling and JSONL pagination loops from the follower workflow. # Set the JSON Lines destination to File.open("xquik-replies.jsonl", "w"). csv_response = client.extractions.export_results(job.id, format_: :csv) csv_response.rewind File.binwrite("xquik-replies.csv", csv_response.read) json_response = client.extractions.export_results(job.id, format_: :json) json_response.rewind File.binwrite("xquik-replies.json", json_response.read) xlsx_response = client.extractions.export_results(job.id, format_: :xlsx) xlsx_response.rewind File.binwrite("xquik-replies.xlsx", xlsx_response.read) ``` `reply_extractor` requires `target_tweet_id`. `client.extractions.retrieve` returns `results`, `has_more`, and `next_cursor`; the shared pagination loop passes `next_cursor` back as `cursor`. 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. `client.extractions.export_results` supports `:csv`, `:json`, and `:xlsx` for file handoff. Cost: 1 credit per reply extracted or returned. ## Workflow: Post Media Tweets and DM Attachments Use this workflow when a Ruby app, Rails job, Sidekiq worker, or agent service needs to post a media-backed tweet, reply with media, or send one uploaded media item in a DM. Tweet and reply media posts use public media URLs directly on `POST /x/tweets`. Send up to 4 image URLs or exactly 1 MP4 video URL up to 100 MB. Do not mix video with other media. Do not upload first when the media URL is already public. Use `client.request` to capture the durable write action. Send a unique `Idempotency-Key`, store `id`, `request.hash`, `billing`, `result`, and `statusUrl`, then poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. ```ruby theme={null} def create_tweet_handoff(client, payload) response = client.request( method: :post, path: "x/tweets", body: payload ) result = response.fetch(:result, {}) || {} { "status" => response.fetch(:status), "terminal" => response.fetch(:terminal), "safe_to_retry" => response.fetch(:safeToRetry), "write_action_id" => response.fetch(:id), "request_hash" => response.dig(:request, :hash), "tweet_id" => result[:id] || response[:tweetId], "charged" => response.dig(:billing, :charged), "charged_credits" => response.dig(:billing, :chargedCredits), "poll" => response.fetch(:terminal) ? nil : response.fetch(:statusUrl) } end tweet_handoff = create_tweet_handoff( client, { account: "@username", text: "New demo video is live.", media: ["https://example.com/product-demo.mp4"] } ) puts(JSON.generate(tweet_handoff)) ``` To post an image reply, add `reply_to_tweet_id`: ```ruby theme={null} reply_handoff = create_tweet_handoff( client, { account: "@username", text: "Here is the requested screenshot.", reply_to_tweet_id: "1893704267862470862", media: ["https://example.com/export-preview.png"] } ) puts(JSON.generate(reply_handoff)) ``` For DM attachments, upload the local file first and pass the returned `media.media_id` as the only `media_ids` item: ```ruby theme={null} File.open("./handoff.png", "rb") do |file| media = client.x.media.upload( account: "@username", file: file ) dm = client.x.dm.send_( "44196397", account: "@username", text: "Here is the asset.", media_ids: [media.media_id] ) dm_handoff = { message_id: dm.message_id, media_id: media.media_id, user_id: "44196397", account: "@username", status: "sent" } puts(JSON.generate(dm_handoff)) end ``` Store `write_action_id` and `charged_credits`, then poll [Get Write Action Status](/api-reference/x-write/get-write-action-status) before retrying a pending tweet or reply. `client.x.tweets.create` returns `tweet.tweet_id` for confirmed-only flows. `client.x.media.upload` returns `media.media_id` for DM attachments, and `client.x.dm.send_` returns `dm.message_id` for support tickets, CRM records, queue jobs, or agent memory. Keep DM body text in private systems. Shared logs, public artifacts, queue status, and agent handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and send status instead of full DM bodies. Leave generated `reply_to_message_id` unset even if SDK params expose it; the REST endpoint rejects DM reply threading. Text-only tweet and reply writes cost 30 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.media_id` values to `client.x.tweets.create`; that method uses `media` with public media URLs. ## Cost, Limits & Retries 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`. Read calls are rate-limited, and `429` responses include `Retry-After`. The client retries connection errors, timeouts, 408, 409, 429, and 5xx responses by default. Handle `XTwitterScraper::Errors::RateLimitError` with backoff, and fix 400, 401, 403, 404, or 422 responses before retrying. ## Error Handling All SDK errors inherit from `XTwitterScraper::Errors::APIError`. Throws `BadRequestError`. Throws `AuthenticationError`. Throws `PermissionDeniedError`. Throws `NotFoundError`. Throws `UnprocessableEntityError`. Throws `RateLimitError`. Throws `InternalServerError`. ```ruby theme={null} begin account = client.account.retrieve rescue XTwitterScraper::Errors::APIConnectionError => e warn("Connection failed: #{e.cause}") rescue XTwitterScraper::Errors::APIStatusError => e warn("HTTP #{e.status}") end ``` ## Pagination Paginated responses expose fields such as `has_next_page`. Pass the endpoint's cursor fields when requesting more pages. ```ruby theme={null} page = client.x.tweets.search(q: "xquik", limit: 20) puts("More results are available") if page.has_next_page ``` ## Webhooks & References * [Search Tweets](/api-reference/x/search-tweets) * [Create Tweet](/api-reference/x-write/create-tweet) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Create Monitor](/api-reference/monitors/create) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [REST API Overview](/api-reference/overview) * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-ruby) # Terraform Provider for X Monitors & Webhooks Source: https://docs.xquik.com/sdks/terraform Install Xquik's Terraform provider and manage X monitors, signed webhooks, tweet actions, API keys, and durable automation state as infrastructure across teams.
For the complete documentation index, see llms.txt.
Use the Terraform provider when Xquik resources need to live beside the rest of your infrastructure code. It is useful when platform teams want repeatable account monitors, HMAC webhooks, API keys, drafts, tweet actions, profile updates, and support workflows in the same review and state process as the rest of their deployment. ## Install Install from the [Terraform Registry](https://registry.terraform.io/providers/Xquik-dev/x-twitter-scraper/latest). Declare the provider: ```hcl theme={null} terraform { required_providers { x-twitter-scraper = { source = "Xquik-dev/x-twitter-scraper" version = "~> 0.7.1" } } } provider "x-twitter-scraper" { api_key = var.xquik_api_key } ``` Run `terraform init` before planning or applying resources. ## Authenticate Use variables or environment variables for credentials: ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" export X_TWITTER_SCRAPER_BEARER_TOKEN="YOUR_OAUTH_TOKEN" ``` Prefer environment variables for credentials. If you pass credentials through Terraform variables, mark them `sensitive = true` and use an encrypted remote state backend. ## Workflow: Monitor Tweets to Signed Webhooks Use this workflow when Terraform should own a production tweet monitor and the webhook endpoint that receives signed events for queues, incident tools, social listening dashboards, or CRM enrichment. The provider resources map to the REST API: `x-twitter-scraper_monitor` creates an account monitor through `POST /monitors`, and `x-twitter-scraper_webhook` creates a signed webhook through `POST /webhooks`. Both resources use Terraform snake\_case arguments that map to the API request fields. ```hcl theme={null} variable "webhook_url" { type = string } variable "xquik_api_key" { type = string sensitive = true } provider "x-twitter-scraper" { api_key = var.xquik_api_key } resource "x-twitter-scraper_webhook" "alerts" { url = var.webhook_url event_types = ["tweet.new", "tweet.reply"] is_active = true } resource "x-twitter-scraper_monitor" "xquik_account" { username = "username" event_types = ["tweet.new", "tweet.reply"] is_active = true } output "monitor_id" { value = x-twitter-scraper_monitor.xquik_account.id } output "webhook_id" { value = x-twitter-scraper_webhook.alerts.id } output "webhook_secret" { value = x-twitter-scraper_webhook.alerts.secret sensitive = true } ``` ### Request Mapping Terraform resource `x-twitter-scraper_monitor` maps to `POST /monitors`. It requires `username` and `event_types`, accepts optional `is_active`, and returns `id`, `x_user_id`, `is_active`, and `created_at` in state. Terraform resource `x-twitter-scraper_webhook` maps to `POST /webhooks`. It requires `url` and `event_types`, accepts optional `is_active`, and returns `id`, `secret`, and `created_at` in state. Terraform data source `x-twitter-scraper_event` reads one stored monitor event. It requires `id` and returns `type`, `username`, `monitor_id`, `occurred_at`, `x_event_id`, and `data`. Valid monitor and webhook event types are `tweet.new`, `tweet.reply`, `tweet.quote`, and `tweet.retweet`. Webhook URLs must be HTTPS and must be reachable from the public internet. ### Returned Data & Handoff After `terraform apply`, hand `monitor_id` to operators who need to pause, update, or delete the monitor through Terraform. Hand `webhook_id` to delivery dashboards and support playbooks. Store `webhook_secret` in a secret manager and use it to verify `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` on every delivery. Production webhook payloads include `deliveryId` and `streamEventId`. Store `deliveryId` for receiver idempotency. Store `streamEventId` when one monitor event must be processed once across retries or endpoint changes. Use `GET /webhooks/{id}/deliveries` to inspect retry state by webhook after apply. Synthetic `webhook.test` requests verify signing only and do not include those production idempotency fields. Webhook operations are free. Active monitors check every second and cost 21 credits per monitor-hour. Creating one requires the username lookup cost and the first monitor hour. Monitors may pause when credits run out. ### State & Secrets Terraform state can contain the webhook `secret` returned at creation time. Use an encrypted backend, restrict state access, and mark every output that references the secret as `sensitive = true`. If the secret is lost or exposed, delete and recreate the webhook so Terraform receives a new signing secret. ## Workflow: Declare Media Tweets and Replies Use `x-twitter-scraper_x_tweet` when an infrastructure change must create one durable tweet or reply as part of a reviewed apply. The resource maps to `POST /x/tweets`, accepts public media URLs in `media`, and stores the confirmed `tweet_id` in Terraform state. ```hcl theme={null} resource "x-twitter-scraper_x_tweet" "launch_reply" { account = "myxhandle" text = "Launch assets are ready" reply_to_tweet_id = "1893456789012345678" media = ["https://example.com/product-demo.mp4"] } output "launch_reply_tweet_id" { value = x-twitter-scraper_x_tweet.launch_reply.tweet_id } ``` ### Tweet Resource Mapping Terraform resource `x-twitter-scraper_x_tweet` maps to `POST /x/tweets`. It requires `account`, accepts optional `text`, `reply_to_tweet_id`, `is_note_tweet`, `community_id`, and `media`, and returns `tweet_id` plus read-only tweet details in state. The `media` argument is for public image URLs or exactly one public MP4 URL. Text-only tweets and replies cost 30 credits, and attached media adds 2 credits per started MB. Do not pass uploaded media IDs to this resource; uploaded media IDs are for direct messages through `POST /x/dm/{userId}`. Generated provider docs may list `media_ids` on `x-twitter-scraper_x_tweet`, but `POST /x/tweets` rejects `media_ids`. Use `media` with public image or MP4 URLs for tweets. Reserve uploaded media IDs for one-item DM `media_ids` through REST, MCP tools, or a generated SDK. ### Tweet Handoff After `terraform apply`, hand `tweet_id`, `reply_to_tweet_id`, `account`, and the original `media` URLs to the queue, release record, CRM note, or incident timeline that needs to reconcile the post. Keep the media URLs in your own system so a later state refresh does not become the only audit trail. Terraform state for `x-twitter-scraper_x_tweet` does not expose durable write action fields, media upload resources, or direct-message send resources. Use the REST API, MCP tools, or a generated SDK when a worker must store lifecycle handoffs, upload local media for one-item DM `media_ids`, or store returned DM `message_id`. ## Error Handling Provider errors surface through Terraform plan and apply output. REST API response semantics match the [error handling guide](/guides/error-handling). ## State & Pagination Terraform tracks resource state in your configured backend. Data sources and resources use the generated provider schema; review the generated docs in the [provider repository](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper/tree/main/docs) before adding resources to production state. Terraform should not be the handoff surface for extraction files. For reply, follower, or tweet search exports, use the REST extraction endpoints, CLI, MCP tools, or generated SDKs to write CSV, JSON, XLSX, or JSON Lines files. Keep Terraform state focused on long-lived resources and IDs that must be reviewed through plan and apply. ## Webhooks & References * [REST API Overview](/api-reference/overview) * [API Authentication](/api-reference/authentication) * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [Terraform Registry](https://registry.terraform.io/providers/Xquik-dev/x-twitter-scraper/latest) & [Source](https://github.com/Xquik-dev/terraform-provider-x-twitter-scraper) # TypeScript SDK for Tweet Search & X API Automation Source: https://docs.xquik.com/sdks/typescript Use the Xquik TypeScript SDK to search tweets, export JSON Lines, CSV, or XLSX, post media tweets, upload media, send DMs, and run Node.js X API workers.
For the complete documentation index, see llms.txt.
Use the TypeScript SDK when you want typed request parameters, response models, retries, and autocomplete for Xquik REST API workflows in Node.js, Bun, or server-side TypeScript. Use this page when a TypeScript service must search tweets or export them to JSON Lines, CSV, or XLSX. It can export followers and profiles, post media tweets, send DMs, monitor tweets, or send records downstream. | TypeScript task | SDK call | Save | | ---------------- | ------------------------ | ------------ | | Search tweets | `client.x.tweets.search` | `nextCursor` | | Export followers | `client.extractions.run` | `job.id` | ## Install ```bash theme={null} npm install x-twitter-scraper ``` ## Authenticate ```bash theme={null} export X_TWITTER_SCRAPER_API_KEY="xq_YOUR_KEY_HERE" ``` ```ts theme={null} import XTwitterScraper from "x-twitter-scraper"; const client = new XTwitterScraper({ apiKey: process.env["X_TWITTER_SCRAPER_API_KEY"], }); ``` ## Basic Example Search tweets and write durable JSON Lines handoff rows: ```ts theme={null} import XTwitterScraper from "x-twitter-scraper"; const client = new XTwitterScraper(); const page = await client.x.tweets.search({ q: "from:username webhook OR SDK", limit: 10, }); const tweetRows = page.tweets.map((tweet) => ({ tweet_id: tweet.id, text: tweet.text, author_username: tweet.author?.username, created_at: tweet.createdAt, })); for (const row of tweetRows) { process.stdout.write(`${JSON.stringify(row)}\n`); } ``` ## Workflow: Search Tweets to JSON Lines, CSV, or XLSX This job is for Node.js, Bun, and queue workers that need searchable tweet data in a stable handoff format for queues, data lakes, analyst CSV files, or XLSX workbooks. It calls `GET /x/tweets/search` through `client.x.tweets.search`, uses the generated `TweetSearchParams` shape, and writes each returned tweet as one projected JSON object per line. ```ts theme={null} import XTwitterScraper from "x-twitter-scraper"; const client = new XTwitterScraper(); const query = "from:username webhook OR SDK"; let cursor: string | undefined; let pageIndex = 0; while (true) { const pageCursor = cursor ?? null; const page = await client.x.tweets.search({ q: query, queryType: "Latest", cursor, }); for (const tweet of page.tweets) { const row = { source: "xquik.typescript.search", query, 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, created_at: tweet.createdAt ?? 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, page_index: pageIndex, page_cursor: pageCursor, next_cursor: page.next_cursor || null, has_next_page: page.has_next_page, }; process.stdout.write(`${JSON.stringify(row)}\n`); } if (!page.has_next_page || !page.next_cursor) { break; } cursor = page.next_cursor; pageIndex += 1; } ``` The generated params map directly to the REST endpoint: Maps to REST `q`. Use it for the required X search query with keywords, handles, hashtags, or operators. Maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.has_next_page` is true, keep the same `q`, filters, `queryType`, and `limit` when you continue with `page.next_cursor`. Maps to REST `cursor`. Pass the opaque cursor from `page.next_cursor` to request the next page. Maps to REST `sinceTime`. Use the inclusive ISO bound on every page. Maps to REST `untilTime`. Use the exclusive ISO bound on every page. Maps to REST `queryType`. Use `Latest` for chronological results or `Top` for engagement-ranked results. ## Returned Data & Handoff `client.x.tweets.search` returns `PaginatedTweets`: JSON field `tweets`. Contains tweet records with `id`, `text`, optional `author`, `createdAt`, `likeCount`, `replyCount`, `retweetCount`, `quoteCount`, `bookmarkCount`, `viewCount`, and `isNoteTweet` when available. TypeScript field `page.has_next_page`. JSON field `has_next_page`. Tells your worker whether another page exists. JSON field `next_cursor`. Store it only when `page.has_next_page` is true. For bounded pulls that return fewer tweets than `limit`, pass it back as `cursor` with the same query, filters, `queryType`, and `limit`. Project `page.tweets` into JSON Lines rows in `xquik-tweet-search.jsonl` for queues and data lakes, transform the same projected records into CSV for analysts, or produce XLSX from those rows when account teams need spreadsheets. Store `tweet_id`, `author_username`, engagement counts, `page_index`, `page_cursor`, `next_cursor`, and `has_next_page` so a worker can resume from the last saved cursor without replaying raw SDK objects. For explicit `limit` pulls, resume with the same query, filters, `queryType`, and `limit`; only `cursor` changes. ## Workflow: Follower Export to CSV, JSON, or XLSX Use this workflow when a Node.js, Bun, or queue worker needs an owned follower list for CRM import, warehouse loading, account scoring, analyst CSV, XLSX workbook delivery, or a resumable JSON handoff. It calls `POST /extractions/estimate` through `client.extractions.estimateCost`, creates the job with `client.extractions.run`, reads saved rows with `client.extractions.retrieve`, and downloads files with `client.extractions.exportResults`. `client.extractions.run` returns the queued `202 Accepted` receipt from `POST /extractions`: `id`, `toolType`, and `status: "running"`. Store `job.id` immediately, then poll `client.extractions.retrieve` before reading pages or calling `client.extractions.exportResults`. Credit reservation happens after the job starts. If available credits changed since `estimateCost`, the API can set `resultsLimit` to the affordable count before fetching rows or mark the job `failed` with `insufficient_credits`. ```ts theme={null} import { open, writeFile } from "node:fs/promises"; import XTwitterScraper from "x-twitter-scraper"; const client = new XTwitterScraper(); const targetUsername = "username"; const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); const estimate = await client.extractions.estimateCost({ toolType: "follower_explorer", targetUsername, }); if (!estimate.allowed) { throw new Error("Insufficient credits for follower export."); } const job = await client.extractions.run({ toolType: "follower_explorer", targetUsername, }); while (true) { const statusPage = await client.extractions.retrieve(job.id, { limit: 1 }); const status = statusPage.job["status"]; if (status === "completed") { break; } if (status === "failed") { throw new Error("Follower export failed."); } await sleep(10000); } let cursor: string | undefined; const jsonl = await open("xquik-followers.jsonl", "w"); try { while (true) { const page = await client.extractions.retrieve(job.id, { limit: 1000, cursor, }); for (const row of page.results) { await jsonl.write(`${JSON.stringify(row)}\n`); } if (!page.hasMore || !page.nextCursor) { break; } cursor = page.nextCursor; } } finally { await jsonl.close(); } const csv = await client.extractions.exportResults(job.id, { format: "csv" }); await writeFile("xquik-followers.csv", Buffer.from(await csv.arrayBuffer())); const json = await client.extractions.exportResults(job.id, { format: "json" }); await writeFile("xquik-followers.json", Buffer.from(await json.arrayBuffer())); const xlsx = await client.extractions.exportResults(job.id, { format: "xlsx" }); await writeFile("xquik-followers.xlsx", Buffer.from(await xlsx.arrayBuffer())); ``` `follower_explorer` requires `targetUsername`. Persist `job.id`, `targetUsername`, `estimate.estimatedResults`, and `estimate.source` before polling so a queue retry, worker restart, or agent handoff can resume the same export. `client.extractions.retrieve` returns `results`, `hasMore`, and `nextCursor`; pass `nextCursor` back as `cursor` when you need stored JSON pages before exporting files. 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. Map exported `User ID` or row `xUserId` as the CRM unique key. Cost: 1 credit per follower extracted or returned. Exports are free after the extraction job exists. ## Workflow: Tweet Replies to CSV, JSON, or XLSX Use this workflow when a TypeScript worker needs every reply under one tweet as a saved extraction, JSON Lines handoff, or CSV/JSON/XLSX export. It uses `client.extractions.estimateCost`, `run`, `retrieve`, and `exportResults`. Reuse the polling, JSON Lines pagination, and export structure from the follower workflow. Only the tool type, target field, and filenames change: ```ts theme={null} const targetTweetId = "1893704267862470862"; const estimate = await client.extractions.estimateCost({ toolType: "reply_extractor", targetTweetId, }); if (!estimate.allowed) { throw new Error("Insufficient credits for reply extraction."); } const job = await client.extractions.run({ toolType: "reply_extractor", targetTweetId, }); // Run the same polling and JSONL pagination loops from the follower workflow. // Set the JSON Lines destination to await open("xquik-replies.jsonl", "w"). const csv = await client.extractions.exportResults(job.id, { format: "csv" }); await writeFile("xquik-replies.csv", Buffer.from(await csv.arrayBuffer())); const json = await client.extractions.exportResults(job.id, { format: "json" }); await writeFile("xquik-replies.json", Buffer.from(await json.arrayBuffer())); const xlsx = await client.extractions.exportResults(job.id, { format: "xlsx" }); await writeFile("xquik-replies.xlsx", Buffer.from(await xlsx.arrayBuffer())); ``` `reply_extractor` requires `targetTweetId`. `client.extractions.retrieve` returns `results`, `hasMore`, and `nextCursor`; the shared pagination loop passes `nextCursor` back as `cursor`. 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. `client.extractions.exportResults` supports `csv`, `json`, and `xlsx` for file handoff. Cost: 1 credit per reply extracted or returned. ## Workflow: Post Media Tweets and DM Attachments Use this workflow when a TypeScript worker, support queue, or agent service needs to post a media-backed tweet, reply with media, or send one uploaded media item in a DM. Tweet and reply media posts use public media URLs directly on `client.x.tweets.create`. Send up to 4 image URLs or exactly 1 MP4 video URL up to 100 MB. Do not mix video with other media. Do not upload first when the media URL is already public. ```ts theme={null} interface TweetWriteAction { id: string; status: string; terminal: boolean; safeToRetry: boolean; statusUrl: string; request: { hash: string | null }; billing: { charged: boolean; chargedCredits: string }; result: { id?: string } | null; tweetId?: string; } function createTweetHandoff( action: TweetWriteAction, base: { account: string; media: string[]; reply_to_tweet_id?: string; }, ) { const thread = base.reply_to_tweet_id ? { reply_to_tweet_id: base.reply_to_tweet_id } : {}; return { status: action.status, terminal: action.terminal, safe_to_retry: action.safeToRetry, write_action_id: action.id, request_hash: action.request.hash, tweet_id: action.result?.id ?? action.tweetId ?? null, charged: action.billing.charged, charged_credits: action.billing.chargedCredits, poll: action.terminal ? null : action.statusUrl, ...base, ...thread, }; } ``` Capture the durable write action from the raw response. Send a unique `Idempotency-Key`, store `id`, `request.hash`, `billing`, `result`, and `statusUrl`, then poll while `terminal` is false. Retry only when `safeToRetry` is true, using a new key. ```ts theme={null} const tweet = (await client.x.tweets.create({ account: "@username", text: "New demo video is live.", media: ["https://example.com/product-demo.mp4"], })) as TweetWriteAction; const tweetHandoff = createTweetHandoff(tweet, { account: "@username", media: ["https://example.com/product-demo.mp4"], }); process.stdout.write(`${JSON.stringify(tweetHandoff)}\n`); ``` To post an image reply, add `reply_to_tweet_id`: ```ts theme={null} const reply = (await client.x.tweets.create({ account: "@username", text: "Here is the requested screenshot.", reply_to_tweet_id: "1893704267862470862", media: ["https://example.com/export-preview.png"], })) as TweetCreateResult; const replyHandoff = createTweetHandoff(reply, { account: "@username", reply_to_tweet_id: "1893704267862470862", media: ["https://example.com/export-preview.png"], }); process.stdout.write(`${JSON.stringify(replyHandoff)}\n`); ``` For DM attachments, upload the local file first and pass the returned `media.mediaId` as the only `media_ids` item: ```ts theme={null} import fs from "node:fs"; const media = await client.x.media.upload({ account: "@username", file: fs.createReadStream("./handoff.png"), }); const dm = await client.x.dm.send("44196397", { account: "@username", text: "Here is the asset.", media_ids: [media.mediaId], }); const dmHandoff = { status: "sent", message_id: dm.messageId, media_id: media.mediaId, account: "@username", user_id: "44196397", }; process.stdout.write(`${JSON.stringify(dmHandoff)}\n`); ``` `client.x.tweets.create` returns `tweet.tweetId` for confirmed posts or the pending write fields above when confirmation is still running. `client.x.media.upload` returns `media.mediaId` for DM attachments, and `client.x.dm.send` returns `dm.messageId` for support tickets, CRM records, queue jobs, or agent memory. Keep DM body text in private systems. Shared logs, public artifacts, queue status, and agent handoffs should store `message_id`, optional `media_id`, `account`, `user_id`, and send status instead of full DM bodies. Leave `reply_to_message_id` unset even if generated SDK types expose it; the REST endpoint rejects DM reply threading. Text-only tweet and reply writes cost 30 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 `media` with public media URLs. Useful endpoints: * [Search Tweets](/api-reference/x/search-tweets) * [Create Extraction](/api-reference/extractions/create) * [Get Extraction](/api-reference/extractions/twitter-extraction-results) * [Export Extraction](/api-reference/extractions/export) * [Get User](/api-reference/x/twitter-profile-lookup) * [Create Tweet](/api-reference/x-write/create-tweet) * [Upload Media](/api-reference/x-write/upload-media) * [Send Direct Message](/api-reference/x-write/send-dm) * [Create Monitor](/api-reference/monitors/create) * [Create Webhook](/api-reference/webhooks/create) ## Error Handling The SDK throws typed errors for API failures: Throws `BadRequestError`. Throws `AuthenticationError`. Throws `PermissionDeniedError`. Throws `NotFoundError`. Throws `UnprocessableEntityError`. Throws `RateLimitError`. Throws `InternalServerError`. ```ts theme={null} import XTwitterScraper from "x-twitter-scraper"; const client = new XTwitterScraper(); try { await client.account.retrieve(); } catch (error) { if (error instanceof XTwitterScraper.APIError) { process.stderr.write(`HTTP ${error.status}\n`); } else { process.stderr.write("Network or timeout error\n"); } } ``` The client retries connection errors, 408, 409, 429, and 5xx responses by default. Set `maxRetries` to tune retry behavior. ## Cost, Limits & Retries 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`. Read calls are rate-limited, and `429` responses include `Retry-After`. The client retries connection errors, 408, 409, 429, and 5xx responses by default. Handle `RateLimitError` with backoff, and fix 400, 401, 403, 404, or 422 responses before retrying. ## Pagination Search and list endpoints return page objects. Check `has_next_page` and pass the generated cursor fields documented on each endpoint when you need another page. ```ts theme={null} const firstPage = await client.x.tweets.search({ q: "xquik", limit: 20 }); if (firstPage.has_next_page) { process.stderr.write("More results are available\n"); } ``` ## Webhooks & References * [Webhook Overview](/webhooks/overview) * [Verify HMAC Signatures](/webhooks/verification) * [Twitter Scraper API Overview](/api-reference/overview) * Download the OpenAPI schema: `curl -o openapi.json https://xquik.com/openapi.json` * [Source Repository](https://github.com/Xquik-dev/x-twitter-scraper-typescript) # Security Reports for Xquik API, MCP & Webhooks Source: https://docs.xquik.com/security Report vulnerabilities affecting Xquik docs, API keys, REST requests, MCP, OAuth, webhooks, SDKs, account connections, or exports. Includes exact examples. Use [private vulnerability reporting](https://github.com/Xquik-dev/xquik-docs/security/advisories/new). Email [security@xquik.com](mailto:security@xquik.com) if GitHub is unavailable. Do not open a public issue, discussion, or pull request for a vulnerability. ## What to Include Include these details when available: * A clear description of the issue * Reproduction steps and affected URLs * Request and response samples with secrets removed * The affected endpoint, SDK version, or documentation page * The expected impact * A suggested mitigation Never send API keys, passwords, session cookies, or personal data. ## Response Targets Xquik reviews private reports against these targets: * Acknowledgement within 24 hours * Initial triage within 72 hours * A mitigation plan after triage * Progress updates at least every 14 days Critical issues receive immediate priority. ## Scope The security contact covers: * `docs.xquik.com` * The Xquik REST API * The Xquik MCP server * OAuth 2.1 flows * Webhook signature verification * Published Xquik SDKs Send product support questions to [support@xquik.com](mailto:support@xquik.com). ## Threat Model Protected assets include contract integrity and release metadata. Repository changes, build inputs, links, and deployment cross trust boundaries. Tests detect public contract drift. Pinned workflows and lockfile integrity protect documentation builds. ## Safe Harbor Xquik supports good-faith security research that avoids privacy violations, data destruction, and service interruption. Allow reasonable time for remediation before public disclosure. Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp. # Twitter Scraper & X API Alternative Comparison Source: https://docs.xquik.com/twitter-api-alternatives Compare Xquik with X APIs, tweet scrapers, follower exporters, creator schedulers, social suites, automation builders, and agent tools. Compare API coverage.
For the complete documentation index, see llms.txt.
Compare Xquik with X APIs, tweet scrapers, follower exporters, creator tools, and social suites. Each guide compares specific tasks, testable outputs, and verified costs. These guides are factual comparison and migration references. Use them with the API reference, integration guides, and official product pages. ## How to Choose the Best Twitter Scraper API The best Twitter scraper API depends on the exact records and handoff. Start with tweets, followers, following, replies, profiles, timelines, communities, lists, media, or monitors. Then compare pagination, files, webhooks, SDKs, MCP, retries, and maintenance. Do not choose from a generic top-tools list alone. Test the same query, profile, or export in each candidate. Compare returned fields, missing records, cursor behavior, failure recovery, and total cost. ## Twitter API Alternative Questions for 2026 ### What Is the Best API to Scrape Twitter Data in 2026? Replace the word data with the records your workflow needs. List tweets, replies, reposts, likes, profiles, followers, following, timelines, media, communities, or lists. Then test one real request against every candidate. Check returned Tweet IDs, user IDs, timestamps, engagement counts, media URLs, and cursor fields. Measure missing rows, duplicate rows, response time, and retry behavior. Price the same result count separately. Xquik supports direct cursor pages, saved extraction jobs, and file exports. It also supports monitors, signed webhooks, SDKs, and MCP. Use only the surfaces the tested workflow needs. Recheck limits, prices, and contracts before choosing. ### What Is the Best Twitter Scraper API for Developers in 2026? No single Twitter API fits every export. Compare supported searches, tweet fields, author fields, rate limits, and cursor behavior. Then compare SDKs, costs, and file formats. Check for tweet text, replies, reposts, and likes. Also check media, profiles, and stable Tweet IDs. Xquik fits developers who need direct Twitter search and saved export jobs. The direct route returns cursor pages. Extraction jobs add estimates, receipts, status polling, and CSV, JSON, or XLSX files. Both paths use one API key. Use direct search for low-latency pages. Use extraction jobs for repeatable exports. Use account or keyword monitors for real-time alerts. This separation keeps each request contract clear. Test the complete integration before choosing. Include API-key storage, query operators, date ranges, and cursor checkpoints. Test rate limits, `429` recovery, temporary failures, and row deduplication. Validate every export. Compare Tweet IDs, author IDs, timestamps, text, engagement counts, media, and URLs. The best developer API makes those records and recovery steps predictable. ### Best Twitter API 2026 Choose the best Twitter API for one defined production workflow. Tweet search needs query operators, stable Tweet IDs, author fields, and dates. It also needs engagement counts, media, and cursor pagination. A follower export needs stable user IDs, profile fields, page checkpoints, and file formats. A monitor needs event identity, webhook verification, retries, and replay. Run the same request, result cap, and time window through each shortlisted API. Record complete fields, omitted fields, duplicates, errors, latency, and credits consumed. Xquik fits integrations requiring REST, saved exports, monitors, or signed webhooks. It also provides SDKs, MCP, and Actors. Verify the current contract before selecting any 2026 provider. ### Which Twitter API Alternative Is Easiest to Use? Start with one real task. Test tweet search, follower export, reply export, or a monitor webhook. The easiest API returns required fields with clear authentication, pagination, and errors. It also documents costs and SDK examples. Xquik packages tweet reads, profile reads, and follower or reply exports. It also provides account actions, monitors, signed webhooks, SDKs, MCP, and files. Teams still need the correct endpoint. They must store returned IDs and cursors. Time a complete implementation, not only the first successful request. Include secret storage, cursor checkpoints, rate-limit recovery, and idempotent retries. Also include typed response handling and production alerts. The easiest Twitter API alternative makes those operational steps explicit. It should help diagnose missing tweets, repeated followers, or rejected writes. It should also explain failed webhook delivery without browser scraping. ### Twitter Data API Comparison Make this comparison concrete by naming every required record. For tweets, compare text, author ID, creation time, replies, reposts, and likes. Also compare quotes, views, bookmarks, media, and permalinks. For profiles, compare stable user IDs, usernames, names, bios, and verification. Then compare locations, audience counts, and images. Next compare authentication, search operators, page sizes, cursors, rate limits, errors, and retries. Then compare exports, webhooks, SDKs, and MCP access. Run one fixed query and one fixed follower profile through each API. Store the returned IDs and costs. This produces a reproducible Twitter API comparison instead of a generic feature table. Omit rows lacking current documentation or a controlled test. ### Top Tweet Scraping Tools Evaluate top tweet scraping tools with a repeatable test set. Include a keyword query, hashtag, author filter, language, and date range. Add a media filter and minimum engagement threshold. Check for stable Tweet IDs, full text, author IDs, and timestamps. Then inspect replies, reposts, likes, quotes, media URLs, and the opaque cursor. Also test `429` handling, temporary failures, duplicate prevention, and saved checkpoints. Inspect CSV and JSON exports. Browser scrapers may suit a local experiment. Hosted APIs suit applications requiring documented responses and operational support. Xquik adds live search, extraction jobs, monitors, and webhooks. It also provides SDKs, MCP, and Actors. Compare the same result count and handoff before choosing. ### Best Twitter Scraper API Start with the destination. A spreadsheet workflow needs stable columns and CSV or XLSX output. A warehouse needs JSON rows, stable IDs, timestamps, and replayable checkpoints. A live application needs predictable cursor pages, rate-limit headers, retry instructions, and typed responses. For Xquik, use live Twitter search for current pages. Use an extraction job for an estimate, durable job ID, status, and downloadable files. Use a keyword or account monitor when matching tweets must create events. Test the chosen path with the real query and result cap. The best Twitter scraper API matches the destination through a verified contract. It should not require manual cleanup. ### Twitter API Alternatives 2026 Twitter API alternatives include official access, hosted scraper APIs, and open-source clients. Other options include browser automation, dataset platforms, and social suites. They solve different tasks. Compare tweet search, profile lookup, followers, following, replies, and timelines. Then compare media, communities, lists, writes, monitors, and webhooks. Create a dated scorecard. Record required authentication, returned fields, cursor behavior, rate limits, and file exports. Add SDK coverage, recovery steps, and one fixed workload cost. Test changes before migration. Xquik can combine REST, exports, account actions, monitors, and signed webhooks. It also offers SDKs, MCP, and Actors. That benefit matters only when workflows use those surfaces. ### Is Xquik Better Than the Official Twitter API for Scraping? That depends on the required contract. Choose direct platform access when its tier, authentication, fields, and limits fit. Choose Xquik for hosted extraction jobs, file exports, and credit estimates. It also provides replayable monitor events through one documented integration surface. Compare the same query and result count. Inspect tweet text, authors, profiles, media, replies, engagement fields, cursors, errors, and total cost. Do not base the decision on a feature-list total. Include operational differences. Record API-key or OAuth setup, supported search windows, page sizes, and `429` recovery. Add saved exports and production support. Direct access may fit teams aligned with official tiers and limits. Xquik may fit teams needing hosted jobs, files, monitors, or webhook replay. Keep the conclusion scoped to the test date and workload. ### Xquik vs Apify Twitter Scraper Choose Apify when Actors, datasets, schedules, and the Apify ecosystem own the workflow. Xquik publishes tweet and follower Actors for those handoffs. Choose Xquik REST for direct pages, account actions, monitors, or signed webhooks. REST also supports SDKs and MCP. Test the relevant Actor and REST route with the same target before migrating. Compare Actor input, dataset schema, run status, schedules, retries, storage, and compute charges. Compare them against Xquik request fields, records, credits, and files. Check Tweet IDs, author IDs, engagement counts, media URLs, and pagination. Use Xquik on Apify when Actors and datasets are the desired handoff. Use REST for immediate pages, Xquik monitors, or webhooks. ### Xquik vs Twitter API v2 Compare authentication, search coverage, returned fields, pagination, rate limits, exports, webhooks, and write requirements. Official API access offers a direct platform contract. Xquik provides its own documented response contracts and workflow tooling. Keep product claims scoped to the tested task. Review the dedicated [X API comparison](/alternatives/x-api) for the current migration checklist. Test one recent search, user lookup, and follower page where available. Compare stable IDs, tweet fields, profile fields, and time-window rules. Then inspect expansions, cursors, rate limits, and error bodies. Price the required monthly volume. Xquik adds extraction, export, monitor, webhook, SDK, MCP, and Actor contracts. It does not remove X terms or result validation. ### What Makes a Strong Twitter API Comparison? Use one test account, search query, time window, and result cap. Record returned Tweet IDs, author IDs, profile fields, cursors, retry behavior, and elapsed time. Price the same workload instead of comparing unrelated plan labels. Repeat the test after any material API or pricing change. A 2026 comparison must identify its test date and source links. Keep unsupported claims out of the buying brief. ## Direct Buying Rules Do not compare logos. Compare the task, output, integration handoff, and cost. Choose Xquik when the X part of the task must move into code, exports, signed webhooks, SDKs, MCP, or repeatable dashboard tools. Compare the exact workload. X API uses credits, Buffer prices by channel, creator tools cap accounts or posts, and social suites charge for broader team features. Xquik returns usable records: tweet search results, followers, replies, likes, media links, monitor events, webhook payloads, exports, and API responses. Start in the dashboard, then automate the same task through REST, signed webhooks, 10 SDKs, exports, or MCP. No separate scheduler, scraper, queue, and webhook toolchain. Xquik starts at USD 20/month with 140,000 credits. Top-ups are USD 0.00015/credit, webhook management is free, and monitors bill only while enabled. ## Xquik value baseline Use this baseline when deciding if Xquik should replace or complement another tool. Choose Xquik for tweet search, profile lookup, follower or reply exports, account actions, monitor events, signed webhooks, and integration handoffs. 23 extraction tools cover tweets, profiles, followers, replies, media, communities, lists, Spaces, and trends. 17 X write tools perform account actions. Radar adds 7 discovery sources. API groups cover tweets, profiles, followers, replies, timelines, X account actions, monitors, webhooks, exports, draws, drafts, billing, and API keys. Start with an API key or dashboard tool, then add signed webhooks, 10 SDKs, MCP, exports, and pay-per-use read endpoints from the same account. Plans start at USD 20/month with included credits. PAYG top-ups cost USD 0.00015/credit, and active monitors bill only while enabled. ## How to use these guides Start with the product your team already uses or the category closest to the task. Validate the exact task, output records, API needs, and handoff points before comparing plans. The docs keep one canonical comparison page for each alternative. ## Sector Decision Matrix Use this matrix when the buying brief starts with a sector, workflow, or team instead of a product name. Start with [X API](/alternatives/x-api), [Twitter API Pro](/alternatives/twitter-api-pro), or [twscrape](/alternatives/twscrape). Choose Xquik when the team needs hosted tweet search, user lookup, follower checks, retries, pagination, SDKs, MCP, and signed webhooks. Test JSON API responses, cursor pagination, webhook payloads, and SDK calls. Start with [Apify](/alternatives/apify), [SocialCrawl](/alternatives/socialcrawl), or [TweetStream](/alternatives/tweetstream). Choose Xquik when the X job needs Xquik on Apify, follower exports, tweet datasets, or a path from datasets to REST, webhooks, and MCP. Test Apify dataset rows, CSV/JSON/XLSX exports, and REST response fields. Start with [Typefully](/alternatives/typefully), [Hypefury](/alternatives/hypefury), or [Tweet Hunter](/alternatives/tweet-hunter). Choose Xquik when publishing must connect to media uploads, direct messages, follower exports, monitor events, and API handoff. Test draft text, media upload result, tweet creation response, and monitor event. Start with [Brandwatch](/alternatives/brandwatch), [Meltwater](/alternatives/meltwater), [Talkwalker](/alternatives/talkwalker), [Hootsuite](/alternatives/hootsuite), [Sprout Social](/alternatives/sprout-social), or [Sprinklr](/alternatives/sprinklr). Choose Xquik when a broad suite is too much. Test tweet search, profile records, follower exports, account actions, monitor events, webhook payloads, and export files. Start with [n8n](/alternatives/n8n), [Make](/alternatives/make), [Zapier](/alternatives/zapier), or [Pipedream](/alternatives/pipedream). Choose Xquik when the workflow builder should keep orchestration while Xquik supplies exact X records, signed webhooks, SDKs, MCP, and exports. Test HTTP step response, signed webhook event, extraction export, and MCP tool result. Start with [Antwork](/alternatives/antwork), [Outstand](/alternatives/outstand), or [TryPost](/alternatives/trypost). Choose Xquik when agents need tweet search, user profiles, monitor events, compose guidance, or normalized MCP pagination. Test MCP tool result, tweet search records, user profile fields, and compose score. ## API & Developer Use X API when you need direct platform access and can build auth, pagination, storage, retries, exports, and alerts. Use Xquik when you want those pieces packaged. Use higher-tier official X API access when you need direct platform control. Use Xquik when the task is tweet search, follower export, monitoring, webhook delivery, or account actions. Use Postproxy when one API must publish to many social networks. Use Xquik when the X part also needs search, follower exports, monitors, signed webhooks, SDKs, or MCP. Use SocialCrawl for cross-network collection. Use Xquik when tweets, profiles, followers, replies, or timelines must connect to account actions, exports, 1-second monitors, signed webhooks, SDKs, and MCP. Use Apify for broad web scraping Actors and datasets. Xquik publishes 8 Actors for focused X data jobs, plus REST, webhook, SDK, and MCP workflows. Use Xanguard for crypto alert streams. Use Xquik when the same team also needs tweet search, user lookups, follower exports, writes, webhooks, SDKs, and MCP. Use TweetStream for a dedicated real-time tweet stream. Use Xquik when you also need extractions, exports, account actions, monitors, signed webhooks, SDKs, and MCP. Use twscrape when you want to own the code and maintenance. Use Xquik when you want hosted endpoints, billing, exports, monitors, webhooks, and support. ## Publishing & Creator Use Typefully for drafting and scheduling threads. Use Xquik when publishing must connect to tweet search, follower exports, media uploads, DMs, monitors, APIs, or MCP. Use Hypefury for creator queues and repurposing. Use Xquik when the task also needs tweet, profile, follower, reply & timeline extraction, account actions, signed webhooks, SDKs, or MCP. Use Buffer for low-cost multi-channel scheduling. Use Xquik when the X task needs raw records, follower exports, write actions, monitors, webhooks, SDKs, or MCP. Use Hootsuite for approvals, inboxes, and reporting across networks. Use Xquik when you need tweet, profile, follower, reply & account actions without a broad social-suite rollout. Use Tweet Hunter for ideas, scheduling, and creator growth. Use Xquik when you also need data exports, monitors, API keys, signed webhooks, SDKs, or MCP. Use Black Magic for creator relationship context. Use Xquik when the task is extracting records, monitoring accounts, sending webhooks, or connecting X actions to code. Use Postwise for writing assistance and scheduling. Use Xquik when publishing must connect to exports, account actions, monitors, signed webhooks, SDKs, or MCP. Use ChirrApp to split long text into threads. Use Xquik when you need to publish, extract replies, export followers, monitor keywords, or automate follow-up actions. Use Post Bridge for cross-posting. Use Xquik when the X task needs search, exports, writes, monitors, webhooks, SDKs, or MCP instead of only scheduling. Use Late or Zernio when one API must post across many networks. Use Xquik when the X part needs lower-cost reads, exports, monitors, signed webhooks, SDKs, and MCP. ## Agency & Enterprise Use Brandwatch for social listening, consumer intelligence, and social media management. Use Xquik when the X job needs tweet search, follower exports, monitor events, webhooks, SDKs, or MCP. Use Meltwater for media monitoring, social listening, and PR workflows. Use Xquik when the X job needs tweet search, follower exports, monitor events, webhooks, SDKs, or MCP. Use Talkwalker for consumer intelligence, social listening, sentiment, and trends. Use Xquik when the X job needs tweet search, follower exports, monitor events, webhooks, SDKs, or MCP. Use Sprout Social for social care, analytics, and team reporting. Use Xquik for tweet search, profile records, follower exports, account actions, monitors, and signed webhook delivery. Use Sprinklr for enterprise CX, listening, and governance. Use Xquik for focused tweet search, profile lookup, follower exports, replies, account actions, and webhooks without a full CX platform. Use Audiense for audience segmentation and intelligence. Use Xquik when you need to fetch, export, monitor, and automate X records directly. ## Workflow Automation Shortlist Use this shortlist when the alternative is an automation builder. Keep it when it owns approvals, schedules, app credentials, or team handoffs. Put Xquik behind it for tweet search, profile lookup, follower exports, reply scraping, account actions, monitor events, signed webhooks, SDKs, or MCP. Use it for self-hosted or cloud workflows, AI agent steps, branching logic, and app orchestration. Put Xquik behind it when the workflow needs tweet search, user lookup, extraction jobs, monitor events, or MCP calls. Use it for visual scenarios, routers, schedules, HTTP modules, and app-to-app operations. Put Xquik behind it when a scenario needs X records, follower exports, media uploads, DMs, or signed monitor webhooks. Use it for code-backed workflows, HTTP triggers, reusable actions, sources, and developer handoffs. Put Xquik behind it when a workflow needs stable X API payloads, extraction polling, webhook verification, or exported files. Use it for no-code Zaps, CRM updates, app routing, REST Hooks, and operator-owned automations. Put Xquik behind it when a Zap needs authenticated X API calls, follower exports, monitor events, or approval-backed X actions. Use it for no-code social automation, scheduled Phantoms, lead lists, and growth workflows. Put Xquik behind it when the X task needs REST, SDK, MCP, CSV/JSON/XLSX exports, or 1-second monitor events. ## Agent & Automation Use Antwork for broad agent-driven social publishing. Use Xquik when agents must search tweets, fetch users, run extractions, post, monitor, and receive webhook events. Use Outstand for social management through MCP. Use Xquik when the MCP tool must call tweet, profile, follower, reply & timeline endpoints, write actions, monitors, exports, and signed webhooks. Use TryPost when you want an open-source scheduler. Use Xquik when you want hosted X reads, writes, extractions, monitors, webhooks, SDKs, and MCP. Use TweetDeck or X Pro for live columns. Use Xquik when the same work must become exports, API calls, monitor events, signed webhooks, SDKs, or MCP tools. Use Taplio for LinkedIn content and relationships. Use Xquik for X search, posting actions, follower exports, monitors, webhooks, SDKs, and MCP. Use n8n to orchestrate many apps. Use Xquik when the X step needs API responses, exports, monitors, webhooks, SDKs, or MCP. Use Make to orchestrate visual scenarios. Use Xquik when the X step needs API responses, exports, monitors, webhooks, SDKs, or MCP. Use Pipedream for code-backed workflow automation. Use Xquik when the X step needs API responses, exports, monitors, webhooks, SDKs, or MCP. Use PhantomBuster for no-code social automation and lead workflows. Use Xquik when the X task needs API responses, exports, monitors, webhooks, SDKs, or MCP. Use Zapier to route work across apps. Use Xquik when the X step needs API responses, exports, monitors, webhooks, SDKs, or MCP. # Twitter Webhooks & Notifications API | Monitor API Source: https://docs.xquik.com/webhooks/overview Deliver account and keyword monitor events to HTTPS endpoints with signed payloads, event filters, retry schedules, and HMAC verification. See event fields.
For the complete documentation index, see llms.txt.
Account and keyword monitors check every second. Webhooks deliver matched events to your server. Every delivery uses an HMAC-SHA256 signature. Use webhooks when `tweet.new`, `tweet.reply`, `tweet.quote`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`, `tweet.hashtag`, `tweet.longform`, `profile.avatar.changed`, `profile.banner.changed`, `profile.name.changed`, `profile.username.changed`, `profile.bio.changed`, `profile.location.changed`, `profile.url.changed`, `profile.verified.changed`, `profile.protected.changed`, `profile.pinned_tweet.changed`, or `profile.unavailable.changed` events from tracked accounts must reach your app without polling. Keyword monitors support tweet event types only. The setup returns a monitor ID, webhook ID, one-time signing secret, and signed JSON deliveries. Webhook operations are free. Active monitors cost 21 credits/hour and include stored events plus webhook delivery. Output: monitor ID, username or query, and selected event types. Cost: 21 credits/hour while active. Output: webhook ID, URL, event types, and one-time `secret`. Cost: free. Output: HTTPS POST with JSON body and HMAC headers. Cost: included with the active monitor. ## Choose the webhook source Create `POST /monitors` when one X account should emit selected tweet and profile event types. Store `monitorId`, `username`, `xUserId`, and `eventTypes`. Create `POST /monitors/keywords` when a query should emit matching tweet events. Store `keywordMonitorId`, `query`, and `eventTypes`. Create `POST /webhooks` after the monitor. Store the webhook `id`, URL, selected `eventTypes`, and one-time `secret` before sending tests. Use `GET /events` for stored monitor events and `GET /webhooks/{id}/deliveries` for delivery attempts. Join on `streamEventId`. ## Quick setup Get webhooks working in 3 steps: Choose an account monitor for one X account, or a keyword monitor for matching query results. ```bash Account monitor theme={null} curl -X POST https://xquik.com/api/v1/monitors \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "username": "elonmusk", "eventTypes": ["tweet.new", "tweet.reply"] }' | jq ``` ```bash Keyword monitor theme={null} curl -X POST https://xquik.com/api/v1/monitors/keywords \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "query": "xquik launch", "eventTypes": ["tweet.new", "tweet.reply"] }' | jq ``` Store the returned monitor `id`; account events include `username`, keyword events include `query`. Provide an HTTPS URL and select which event types to receive. Xquik generates a signing secret. Store it securely, it is only returned once. ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks \ -H "x-api-key: xq_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new", "tweet.reply"] }' | jq ``` **Response:** ```json theme={null} { "id": "15", "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new", "tweet.reply"], "secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "createdAt": "2026-02-24T10:30:00.000Z" } ``` When events arrive, verify the `X-Xquik-Signature` header using your webhook secret to confirm authenticity. See [Signature Verification](/webhooks/verification) for implementation details. Send a test payload before connecting production logic: ```bash theme={null} curl -X POST https://xquik.com/api/v1/webhooks/15/test \ -H "x-api-key: xq_YOUR_KEY_HERE" | jq ``` ## How it works ```text theme={null} Account or keyword monitor event -> Xquik -> Your Webhook Endpoint ``` ## Delivery format Webhook events are delivered as HTTPS POST requests. Each monitor event is sent as its own payload. If one active monitor check finds multiple new matching tweets, expect multiple POST requests, one per matched tweet event, instead of one batched payload. ### Headers `application/json`. Payloads are always JSON. `xquik-webhooks/1.0 (+https://xquik.com)`. Identifies Xquik traffic. Unix epoch milliseconds. Used in the signing string and for replay window enforcement. 16 random bytes in hex. Reject duplicates within the replay window. `sha256=HMAC_HEX_DIGEST`. HMAC-SHA256 of `..`. ### Payload body ```json theme={null} { "eventType": "tweet.new", "schemaVersion": 1, "deliveryId": "502", "streamEventId": "9001", "occurredAt": "2026-02-24T14:22:00.000Z", "username": "elonmusk", "data": { "id": "1893456789012345678", "text": "The future is now.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": false, "isReply": false, "isQuote": false, "createdAt": "2026-02-24T14:22:00.000Z" } } ``` Type `string`. Event type selected by the source monitor and webhook. Type `number`. Webhook payload schema version. Current value is `1`. Type `string`. Webhook delivery attempt ID. Store it as the delivery-level idempotency key and use it for delivery-log correlation. Type `string`. Stored monitor event ID. Store it as the event-level de-dupe key when one monitor event should be processed once across webhook retries or endpoint changes. Type `string`. ISO timestamp for when the event occurred. Type `string`. X username for account monitor events. Omitted for keyword-only monitor events and `webhook.test`. Type `string`. Keyword query that matched the event. Present for keyword monitor events. Type `object`. Raw event object for the monitored tweet activity. The `data` field contains the raw tweet object for the monitor event. Fields may vary by tweet type. ### Receiver storage row After signature verification succeeds, store a compact receiver row before handing the event to workers. Use `deliveryId` for delivery-level retries and `streamEventId` for event-level processing. Do not store endpoint signing values, the raw request body, the raw signature, or full headers in shared incident rows. ```json theme={null} { "record_type": "webhook_receiver_event", "webhook_id": "15", "delivery_id": "502", "stream_event_id": "9001", "event_type": "tweet.new", "source": "account", "username": "elonmusk", "signature_verified": true, "nonce_cache_key": "webhook:15:nonce_hash", "occurred_at": "2026-02-24T14:22:00.000Z", "receiver_status": 202, "event_join": "GET /api/v1/events/9001", "delivery_log": "GET /api/v1/webhooks/15/deliveries" } ``` ## Tweet, follower & profile event shapes Each event type includes a `data` object. Tweet events contain the raw tweet. The `webhook.test` event contains a test message and timestamp. The examples below show the most common fields. ### tweet.new A new original tweet posted by the monitored account. ```json theme={null} { "eventType": "tweet.new", "username": "elonmusk", "data": { "id": "1893456789012345678", "text": "The future is now.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": false, "isReply": false, "isQuote": false, "createdAt": "2026-02-24T14:22:00.000Z" } } ``` ### tweet.quote A quote tweet posted by the monitored account. ```json theme={null} { "eventType": "tweet.quote", "username": "elonmusk", "data": { "id": "1893456789012345679", "text": "Interesting take on this.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": false, "isReply": false, "isQuote": true, "quoted_tweet": { "id": "1893400000000000000", "text": "Our latest launch was a success.", "author": { "userName": "SpaceX" } }, "createdAt": "2026-02-24T15:10:00.000Z" } } ``` ### tweet.reply A reply posted by the monitored account. ```json theme={null} { "eventType": "tweet.reply", "username": "elonmusk", "data": { "id": "1893456789012345680", "text": "Great question. Working on it.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": false, "isReply": true, "isQuote": false, "inReplyToId": "1893411111111111111", "createdAt": "2026-02-24T16:30:00.000Z" } } ``` ### tweet.retweet A retweet posted by the monitored account. ```json theme={null} { "eventType": "tweet.retweet", "username": "elonmusk", "data": { "id": "1893456789012345681", "text": "RT @xai: Exciting news today.", "author": { "id": "44196397", "userName": "elonmusk", "name": "Elon Musk" }, "isRetweet": true, "isReply": false, "isQuote": false, "createdAt": "2026-02-24T17:00:00.000Z" } } ``` ### webhook.test A test payload sent via the [Test Webhook](/api-reference/webhooks/test) endpoint to verify your endpoint is reachable. ```json theme={null} { "eventType": "webhook.test", "data": { "message": "Test delivery from Xquik" }, "timestamp": "2026-02-27T12:00:00.000Z" } ``` ## Retry policy Failed deliveries are retried with exponential backoff (base 1 second, multiplier 2x, max 60 seconds): Delays: 1 second, 2 seconds, then 4 seconds. Delays: 8 seconds, 16 seconds, then 32 seconds. Delay: 60 seconds for each attempt after the cap is reached. Final attempt. If it fails, the delivery is marked as `exhausted`. After the 10th failed attempt, the delivery is marked as `exhausted`. A `410 Gone` response exhausts the delivery immediately. Check delivery status via the [deliveries endpoint](/api-reference/webhooks/deliveries). Webhook configuration also tracks receiver health. `GET /webhooks` returns `deliveryStatus`, `consecutiveFailures`, and `failureHardCap`. When repeated receiver failures reach the hard cap, `deliveryStatus` becomes `needs_attention`. Fix the receiver, then call [Resume Webhook](/api-reference/webhooks/resume) to require a signed test before delivery resumes. Retries are processed in batches, so actual delay may be slightly longer than shown. **Terminal failures:** Return `410 Gone` only when Xquik should stop retrying that delivery. Other non-`2xx` responses and network failures retry until the delivery is exhausted. ## Backfill after a receiver outage Use this handoff after your receiver was down, returned repeated non-`2xx` responses, or exhausted deliveries. Fix the receiver first, then use delivery rows to identify failed attempts and stored event pages to rebuild downstream work. Do not use `410 Gone` for temporary throttling; it exhausts that delivery. ```json theme={null} { "record_type": "webhook_receiver_backfill", "webhook_id": "15", "delivery_log": "GET /api/v1/webhooks/15/deliveries", "resume_endpoint": "POST /api/v1/webhooks/15/resume", "event_backfill_endpoint": "GET /api/v1/events?limit=100&cursor={nextCursor}", "source_filter": "monitorId for account monitors, keywordMonitorId for keyword monitors", "join_key": "delivery.streamEventId == event.id", "resume_fields": [ "deliveryId", "streamEventId", "status", "attempts", "lastStatusCode", "lastError", "deliveryStatus", "consecutiveFailures", "failureHardCap", "nextCursor" ], "stop_when": "hasMore is false", "handoff_state": "receiver_fixed_resume_webhook_page_events_join_deliveries" } ``` Store `nextCursor` after every event page. Reprocess events whose `id` matches failed or exhausted delivery `streamEventId` values, then leave delivered rows alone. Scope event pages with `monitorId` for account monitors or `keywordMonitorId` for keyword monitors when the source is known; omit both only for all-monitor replay. ## Troubleshoot a delivery Use this handoff when a receiver fails tests, repeats failures, or reaches an `exhausted` delivery state. ```json theme={null} { "record_type": "webhook_delivery_troubleshooting", "webhook_id": "15", "signed_test": "POST /api/v1/webhooks/15/test", "resume_endpoint": "POST /api/v1/webhooks/15/resume", "delivery_log": "GET /api/v1/webhooks/15/deliveries", "event_join": "GET /api/v1/events/{id}", "event_id_source": "streamEventId", "route_on": [ "deliveryStatus", "consecutiveFailures", "failureHardCap", "status", "attempts", "lastStatusCode", "lastError", "createdAt", "deliveredAt" ], "test_payload_has_ids": false, "production_payload_ids": ["deliveryId", "streamEventId"], "handoff_state": "verify_signature_resume_if_needed_check_delivery_join_event" } ``` Run [Test Webhook](/api-reference/webhooks/test) after changing endpoint code, secrets, firewall rules, or queue routing. Treat `success: true` with a `2xx` `statusCode` as receiver proof. Verify `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` on the raw request body. `webhook.test` omits `deliveryId` and `streamEventId`; production deliveries include both IDs. Check [List Deliveries](/api-reference/webhooks/deliveries) for `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt`. Page on `exhausted`, warn on repeated `failed`, and ignore `delivered`. If `deliveryStatus` is `needs_attention`, fix the receiver, then call [Resume Webhook](/api-reference/webhooks/resume). Delivery resumes only after the signed test succeeds. Join `streamEventId` to [Get Event](/api-reference/events/get) when the receiver owner needs the original monitor event, tweet fields, username, or keyword query that triggered the delivery. ## Requirements * Endpoint **must** use HTTPS * Endpoint **must not** resolve to a private or internal IP address (localhost, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x) * Endpoints should respond promptly with a `2xx` status code. Non-`2xx` responses count as failures and trigger retries ## Where to go next Create, list, update, deactivate, test, resume, and inspect webhook deliveries. Verify HMAC-SHA256 signatures and implement idempotency. Test webhook delivery locally with tunnels and mock payloads. Use `xquik.request('/api/v1/webhooks', ...)` for create, list, update, delete, and test. Create account monitors that emit events for webhook delivery. Create keyword monitors that emit matching tweet events for webhook delivery. # Twitter Webhook Signature Verification | Monitor API Source: https://docs.xquik.com/webhooks/verification Verify HMAC-SHA256 signatures before processing account or keyword monitor webhooks. Preserve raw bodies and compare signatures safely. See event fields.
For the complete documentation index, see llms.txt.
Every webhook delivery is signed with HMAC-SHA256 over a string built from a timestamp, nonce, and the raw body. Always verify the signature AND reject stale or replayed requests before processing events. | Verification checkpoint | Exact source | Reject when | | ----------------------- | ---------------------- | ------------------------------------------------------------ | | Signature | `X-Xquik-Signature` | HMAC-SHA256 differs in a timing-safe comparison. | | Timestamp | `X-Xquik-Timestamp` | The delivery falls outside the 5-minute tolerance. | | Nonce | `X-Xquik-Nonce` | The webhook already used this nonce within 5 minutes. | | Raw body | Unparsed request bytes | Middleware parsed or re-serialized JSON before verification. | | Delivery | `deliveryId` | The receiver already queued this webhook attempt. | | Monitor event | `streamEventId` | The receiver already processed this monitor event. | ## Headers sent with every delivery Read these headers from each webhook `POST` before parsing the JSON body. The signature is the trust boundary; use `Content-Type` and `User-Agent` only for handler routing and logs. `X-Xquik-Timestamp` is Unix epoch milliseconds. Reject requests outside the 5-minute tolerance window. `X-Xquik-Nonce` is 16 random bytes in hex. Store recent values for 5 minutes and reject repeats. `X-Xquik-Signature` is `sha256=` over `..` keyed with the endpoint secret. `Content-Type` is always `application/json`. Verify the raw body bytes before parsing or re-serializing JSON. `User-Agent` is `xquik-webhooks/1.0 (+https://xquik.com)`. Log it for diagnostics, but never trust it instead of the signature. ## How it works 1. Xquik computes the signing string `..`. 2. Xquik computes `sha256=` + HMAC-SHA256(webhook secret, signing string). 3. Your server recomputes the signature with the same secret over the raw body. 4. Reject the request if the signature does not match in constant time. 5. Reject the request if the timestamp is older than 5 minutes (clock-skew tolerant). 6. Reject the request if the nonce was already seen in the last 5 minutes (replay protection). ## Receiver hardening handoff Use this handoff when you promote a webhook receiver from a signed test request to production monitor events. It keeps signing, replay protection, idempotency, and incident routing visible in one review record. ```json theme={null} { "workflow": "webhook_receiver_hardening", "signature": { "raw_body": true, "signing_string": "..", "headers": [ "X-Xquik-Signature", "X-Xquik-Timestamp", "X-Xquik-Nonce" ], "replay_window_ms": 300000, "secret_logging": "never" }, "nonce_store": { "key": "webhook_id:nonce", "ttl_seconds": 300, "on_duplicate": "reject" }, "production_idempotency": { "delivery_id": "502", "stream_event_id": "9002", "test_payload_omits_ids": true }, "incident_row": { "source": "GET /api/v1/webhooks/15/deliveries", "fields": [ "status", "attempts", "lastStatusCode", "lastError", "createdAt", "deliveredAt" ] }, "handoff_state": "verify_raw_body_store_nonce_then_dedupe_delivery" } ``` Keep raw request bytes available until signature verification succeeds. Parse JSON only after the HMAC check passes. Store each nonce with the webhook ID for 5 minutes. Reject duplicates before queueing or acknowledging the event. Store the webhook secret once, use it only for HMAC checks, and never write it to logs, queues, traces, or incident rows. Send `status`, `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and `deliveredAt` from the deliveries API to your alerting or support queue. ## Implementation ```bash cURL (test) theme={null} # Generate a test signature to verify your implementation TS=$(date +%s)000 NONCE=$(openssl rand -hex 16) BODY='{"test":"payload"}' SIG=$(printf "%s.%s.%s" "$TS" "$NONCE" "$BODY" | openssl dgst -sha256 -hmac "your_webhook_secret" | sed 's/.*= /sha256=/') echo "X-Xquik-Timestamp: $TS" echo "X-Xquik-Nonce: $NONCE" echo "X-Xquik-Signature: $SIG" ``` ```javascript Node.js theme={null} import { createHmac, timingSafeEqual } from "node:crypto"; const FIVE_MINUTES_MS = 5 * 60 * 1000; const seenNonces = new Map(); // nonce -> expiresAt function verifyWebhook(req, secret) { const timestamp = req.headers["x-xquik-timestamp"]; const nonce = req.headers["x-xquik-nonce"]; const signature = req.headers["x-xquik-signature"]; const rawBody = req.body.toString(); if (!timestamp || !nonce || !signature) return false; // Reject stale requests (clock-skew tolerant). const ts = Number(timestamp); if (!Number.isFinite(ts) || Math.abs(Date.now() - ts) > FIVE_MINUTES_MS) { return false; } const signingString = `${timestamp}.${nonce}.${rawBody}`; const expected = "sha256=" + createHmac("sha256", secret).update(signingString).digest("hex"); const expectedBuffer = Buffer.from(expected); const signatureBuffer = Buffer.from(signature); if (expectedBuffer.length !== signatureBuffer.length) return false; if (!timingSafeEqual(expectedBuffer, signatureBuffer)) return false; // Claim the nonce only after authentication succeeds. const now = Date.now(); for (const [n, exp] of seenNonces) if (exp <= now) seenNonces.delete(n); if (seenNonces.has(nonce)) return false; seenNonces.set(nonce, now + FIVE_MINUTES_MS); return true; } // Express example app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => { if (!verifyWebhook(req, WEBHOOK_SECRET)) { return res.status(401).send("Invalid signature"); } const event = JSON.parse(req.body.toString()); // Process event... res.status(200).send("OK"); }); ``` ```python Python theme={null} import hmac import hashlib import time from flask import Flask, request app = Flask(__name__) FIVE_MINUTES_MS = 5 * 60 * 1000 _seen_nonces: dict[str, int] = {} def verify_webhook(req, secret: str) -> bool: timestamp = req.headers.get("X-Xquik-Timestamp", "") nonce = req.headers.get("X-Xquik-Nonce", "") signature = req.headers.get("X-Xquik-Signature", "") raw_body = req.get_data() if not (timestamp and nonce and signature): return False try: ts = int(timestamp) except ValueError: return False now_ms = int(time.time() * 1000) if abs(now_ms - ts) > FIVE_MINUTES_MS: return False signing_string = f"{timestamp}.{nonce}.".encode() + raw_body expected = "sha256=" + hmac.new( secret.encode(), signing_string, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(expected, signature): return False expired = [n for n, exp in _seen_nonces.items() if exp <= now_ms] for n in expired: _seen_nonces.pop(n, None) if nonce in _seen_nonces: return False _seen_nonces[nonce] = now_ms + FIVE_MINUTES_MS return True @app.route("/webhook", methods=["POST"]) def webhook(): if not verify_webhook(request, WEBHOOK_SECRET): return "Invalid signature", 401 event = request.get_json() # Process event... return "OK", 200 ``` ```go Go theme={null} package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "io" "net/http" "strconv" "sync" "time" ) const fiveMinutesMs = int64(5 * 60 * 1000) var ( seenNonces = sync.Map{} ) func verifyWebhook(payload []byte, headers http.Header, secret string) bool { timestamp := headers.Get("X-Xquik-Timestamp") nonce := headers.Get("X-Xquik-Nonce") signature := headers.Get("X-Xquik-Signature") if timestamp == "" || nonce == "" || signature == "" { return false } ts, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { return false } nowMs := time.Now().UnixMilli() diff := nowMs - ts if diff < 0 { diff = -diff } if diff > fiveMinutesMs { return false } signingString := fmt.Sprintf("%s.%s.%s", timestamp, nonce, string(payload)) mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(signingString)) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(expected), []byte(signature)) { return false } seenNonces.Range(func(key, value any) bool { expiresAt, ok := value.(int64) if ok && expiresAt <= nowMs { seenNonces.Delete(key) } return true }) _, replayed := seenNonces.LoadOrStore(nonce, nowMs+fiveMinutesMs) return !replayed } func webhookHandler(w http.ResponseWriter, r *http.Request) { payload, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } if !verifyWebhook(payload, r.Header, webhookSecret) { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } // Process event... fmt.Fprint(w, "OK") } ``` ## Security checklist Never process webhook payloads without verifying the signature first. An unverified payload could be a spoofed request. Use `timingSafeEqual` (Node.js), `hmac.compare_digest` (Python), or `hmac.Equal` (Go). String equality (`===`) is vulnerable to timing attacks. Compute the HMAC over the raw request body bytes, not a re-serialized JSON object. Re-serialization can alter whitespace or key ordering. We recommend responding within 10 seconds. Process events asynchronously if your handler is slow. ## Idempotency Webhook deliveries can be retried on failure, so your endpoint may receive the same event multiple times. Production monitor deliveries include `deliveryId` and `streamEventId`. Use `deliveryId` as the webhook delivery idempotency key. Use `streamEventId` when your system should process one monitor event only once across webhook retries or endpoint changes. Do not hash the raw request body when `deliveryId` is available. `webhook.test` deliveries include `eventType`, `data`, and `timestamp`; they omit monitor idempotency fields. Use them to verify signatures and receiver reachability, then skip production event de-dupe for the test request. ### Production store contract Use a persistent store before acknowledging production events. Scope nonce, delivery, and event keys by webhook ID so retries for one endpoint never block another endpoint. Duplicate `deliveryId` or `streamEventId` checks should return `2xx` after verification, because the receiver has already accepted the work. ```json theme={null} { "record_type": "webhook_receiver_store_contract", "webhook_id": "15", "nonce_key": "webhook:15:nonce_hash", "nonce_ttl_seconds": 300, "delivery_key": "webhook:15:delivery:502", "event_key": "event:9002", "duplicate_delivery_status": 200, "duplicate_event_status": 200, "event_join": "GET /api/v1/events/9002", "ack_after": "signature_verified_and_queue_row_written", "ack_status": 202, "slow_work": "async_worker", "delivery_log": "GET /api/v1/webhooks/15/deliveries", "shared_storage_excludes": [ "endpoint_signing_values", "raw_request_body", "raw_signature", "full_headers" ] } ``` Keep raw request bytes only for signature verification. Store the nonce cache marker, delivery key, event key, join route, and processing status in the shared table or queue. Return `2xx` only after verification and a durable queue write. Move slow enrichment, exports, CRM sync, or alerting to an async worker so the receiver can finish within the 10-second delivery timeout. ```javascript Node.js theme={null} const processedDeliveries = new Set(); const processedEvents = new Set(); app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => { const payload = req.body.toString(); if (!verifyWebhook(req, WEBHOOK_SECRET)) { return res.status(401).send("Invalid signature"); } const event = JSON.parse(payload); if (event.eventType === "webhook.test") { return res.status(200).send("Test accepted"); } if ( typeof event.deliveryId !== "string" || typeof event.streamEventId !== "string" ) { return res.status(400).send("Missing idempotency fields"); } if (processedDeliveries.has(event.deliveryId)) { return res.status(200).send("Delivery already processed"); } processedDeliveries.add(event.deliveryId); if (processedEvents.has(event.streamEventId)) { return res.status(200).send("Event already processed"); } processedEvents.add(event.streamEventId); handleEvent(event); res.status(200).send("OK"); }); ``` ```python Python theme={null} processed_deliveries = set() processed_events = set() @app.route("/webhook", methods=["POST"]) def webhook(): payload = request.get_data() if not verify_webhook(request, WEBHOOK_SECRET): return "Invalid signature", 401 event = request.get_json() if event.get("eventType") == "webhook.test": return "Test accepted", 200 delivery_id = event.get("deliveryId") stream_event_id = event.get("streamEventId") if not isinstance(delivery_id, str) or not isinstance(stream_event_id, str): return "Missing idempotency fields", 400 if delivery_id in processed_deliveries: return "Delivery already processed", 200 processed_deliveries.add(delivery_id) if stream_event_id in processed_events: return "Event already processed", 200 processed_events.add(stream_event_id) handle_event(event) return "OK", 200 ``` ```go Go theme={null} type WebhookEvent struct { Data map[string]any `json:"data"` DeliveryID string `json:"deliveryId"` EventType string `json:"eventType"` StreamEventID string `json:"streamEventId"` } var ( processedDeliveries sync.Map processedEvents sync.Map ) func processSignedWebhook(w http.ResponseWriter, r *http.Request) { payload, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } if !verifyWebhook(payload, r.Header, webhookSecret) { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } var event WebhookEvent if err := json.Unmarshal(payload, &event); err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } if event.EventType == "webhook.test" { fmt.Fprint(w, "Test accepted") return } if event.DeliveryID == "" || event.StreamEventID == "" { http.Error(w, "Missing idempotency fields", http.StatusBadRequest) return } if _, replayed := processedDeliveries.LoadOrStore( event.DeliveryID, true, ); replayed { fmt.Fprint(w, "Delivery already processed") return } if _, seen := processedEvents.LoadOrStore( event.StreamEventID, true, ); seen { fmt.Fprint(w, "Event already processed") return } handleEvent(event) fmt.Fprint(w, "OK") } ``` The in-memory examples above work for single-process servers. In production, use a persistent store (database, Redis) to track processed delivery IDs and event IDs across restarts and multiple instances. # Twitter API Quickstart for Monitors & Webhooks Source: https://docs.xquik.com/x-api-quickstart Build a Twitter API integration with Xquik. Create an API key, authenticate a REST request, monitor tweets every 1 second, and send secure signed webhooks.
For the complete documentation index, see llms.txt.
Use this X API quickstart to build a Twitter API integration with Xquik. First, follow the REST API key authentication example. Then call `GET /account`, monitor tweets every 1 second, and register a Twitter webhook. This REST API integration uses `POST /monitors` and `POST /webhooks`. For accountless reads, use a [guest wallet](/guides/guest-wallets) across 33 routes. You can also use [direct MPP](/mpp/quickstart) across 7 operations. | Integration step | Exact API call | Save for the next step | | -------------------------- | -------------------------- | -------------------------------------------------------- | | Check access and credits | `GET /account` | `plan`, `creditInfo.balance`, and monitor billing | | Monitor tweets and replies | `POST /monitors` | Monitor `id`, `username`, `eventTypes`, and `enabled` | | Register the receiver | `POST /webhooks` | Webhook `id`, `url`, `eventTypes`, and one-time `secret` | | Test delivery | `POST /webhooks/{id}/test` | HTTP status and receiver log timestamp | | Verify live delivery | HMAC-SHA256 verification | `deliveryId` and `streamEventId` idempotency keys | ## How the Integration Works Your first API request checks credentials, credits, and monitor billing. Send the `x-api-key` request header for API key authentication. Each write sends `Content-Type: application/json` with a JSON request body. The monitor watches one X account for selected tweet events. The Twitter API webhook step stores your HTTPS API endpoint and chosen webhook events. Signed webhooks then deliver new tweets and replies to that endpoint. Verify every delivery with the saved secret before processing its payload. This flow creates one complete REST integration. The API call confirms access. The monitor detects tweets. The webhook forwards each matching event. Keep the returned monitor and webhook IDs for later updates, tests, or deletion. ## Before You Start * Create an Xquik account with enough credits for the monitor. * Prepare a secret manager for the API key and webhook secret. * Expose an HTTPS endpoint that can receive webhook events. * Choose the X username and exact tweet event types to monitor. * Use an HTTP client that can send JSON requests and headers. Keep the 2 credentials separate. The API key grants Xquik API access. The webhook secret verifies deliveries sent to your server. Never send the Xquik key to the webhook endpoint. Treat the webhook secret as a separate secret key. Call `GET /account` to confirm authentication, plan status, available credits, and monitor billing. Create an account monitor that checks every 1 second. Active monitors cost 21 credits per hour while enabled. Register an HTTPS endpoint and save the one-time secret for HMAC signature verification. Prepay 33 eligible GET routes with a $10-$250 USD hosted checkout. No account required. Sign up at [xquik.com](https://xquik.com) with your email. You'll receive a magic link, no password required. Metered account operations require enough available credits. An active plan is not required while sufficient credits remain. Subscribe for monthly credits or use your remaining account balance. Starter is USD 20/month and includes 140,000 monthly credits. Manage funding from the [dashboard billing page](https://dashboard.xquik.com/en/account?tab=subscription). Guest wallets cover 33 prepaid GET routes without an account. Direct MPP covers 7 fixed-price operations. Open [API Keys](https://dashboard.xquik.com/en/account?tab=api-keys) in the dashboard and create a new key. Copy the full key immediately - it's only shown once. ```text theme={null} xq_your_api_key_here ``` Store your API key securely. It cannot be retrieved after creation. Revoke and replace if compromised. Verify your setup by fetching your account info: ```bash cURL theme={null} curl -s https://xquik.com/api/v1/account \ -H "x-api-key: xq_your_api_key_here" | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/account", { headers: { "x-api-key": "xq_your_api_key_here" }, }); const account = await response.json(); process.stdout.write(`${JSON.stringify(account, null, 2)}\n`); ``` ```python Python theme={null} import requests response = requests.get( "https://xquik.com/api/v1/account", headers={"x-api-key": "xq_your_api_key_here"}, ) print(response.json()) ``` ```go Go theme={null} package main import ( "fmt" "io" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://xquik.com/api/v1/account", nil) req.Header.Set("x-api-key", "xq_your_api_key_here") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` **Response:** ```json theme={null} { "plan": "active", "monitorsAllowed": 9007199254740991, "monitorsUsed": 0, "monitorBilling": { "activeDailyEstimate": "0", "activeHourlyBurn": "0", "creditsPerActiveMonitorDay": "500", "creditsPerActiveMonitorHour": "21", "eventsIncluded": true, "instantCheckIntervalSeconds": 1, "unlimitedSlots": true }, "creditInfo": { "balance": "50000", "lifetimePurchased": "140000", "lifetimeUsed": "90000", "autoTopupEnabled": false, "autoTopupAmountDollars": 10, "autoTopupThreshold": "50000" } } ``` Start tracking an X account: ```bash cURL theme={null} curl -s -X POST https://xquik.com/api/v1/monitors \ -H "x-api-key: xq_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"username": "elonmusk", "eventTypes": ["tweet.new", "tweet.reply"]}' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/monitors", { method: "POST", headers: { "x-api-key": "xq_your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ username: "elonmusk", eventTypes: ["tweet.new", "tweet.reply"], }), }); const monitor = await response.json(); process.stdout.write(`${JSON.stringify(monitor, null, 2)}\n`); ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/monitors", headers={"x-api-key": "xq_your_api_key_here"}, json={ "username": "elonmusk", "eventTypes": ["tweet.new", "tweet.reply"], }, ) print(response.json()) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "username": "elonmusk", "eventTypes": []string{"tweet.new", "tweet.reply"}, }) req, _ := http.NewRequest("POST", "https://xquik.com/api/v1/monitors", bytes.NewReader(body)) req.Header.Set("x-api-key", "xq_your_api_key_here") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) fmt.Println(string(respBody)) } ``` **Response:** ```json theme={null} { "id": "7", "username": "elonmusk", "xUserId": "44196397", "eventTypes": ["tweet.new", "tweet.reply"], "isActive": true, "createdAt": "2026-02-24T10:30:00.000Z", "nextBillingAt": "2026-02-24T10:30:00.000Z" } ``` Receive signed monitor events at your endpoint: ```bash cURL theme={null} curl -s -X POST https://xquik.com/api/v1/webhooks \ -H "x-api-key: xq_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"url": "https://your-server.com/webhook", "eventTypes": ["tweet.new"]}' | jq ``` ```javascript Node.js theme={null} const response = await fetch("https://xquik.com/api/v1/webhooks", { method: "POST", headers: { "x-api-key": "xq_your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ url: "https://your-server.com/webhook", eventTypes: ["tweet.new"], }), }); const webhook = await response.json(); const webhookSecret = webhook.secret; if (!webhookSecret) throw new Error("missing webhook secret"); process.stdout.write(`Webhook ${webhook.id} ready\n`); // Store webhookSecret in your secret manager; do not print it. ``` ```python Python theme={null} import requests response = requests.post( "https://xquik.com/api/v1/webhooks", headers={"x-api-key": "xq_your_api_key_here"}, json={ "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new"], }, ) webhook = response.json() webhook_secret = webhook["secret"] if not webhook_secret: raise RuntimeError("missing webhook secret") print(f"Webhook {webhook['id']} ready") # Store webhook_secret in your secret manager; do not print it. ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]interface{}{ "url": "https://your-server.com/webhook", "eventTypes": []string{"tweet.new"}, }) req, _ := http.NewRequest("POST", "https://xquik.com/api/v1/webhooks", bytes.NewReader(body)) req.Header.Set("x-api-key", "xq_your_api_key_here") req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var webhook struct { ID string `json:"id"` Secret string `json:"secret"` } if err := json.NewDecoder(resp.Body).Decode(&webhook); err != nil { panic(err) } webhookSecret := webhook.Secret if webhookSecret == "" { panic("missing webhook secret") } fmt.Printf("Webhook %s ready\n", webhook.ID) // Store webhookSecret in your secret manager; do not print it. } ``` **Response:** ```json theme={null} { "id": "15", "url": "https://your-server.com/webhook", "eventTypes": ["tweet.new"], "secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "createdAt": "2026-02-24T10:30:00.000Z" } ``` Save the `secret` from the response in a secret manager. Xquik returns it once. Use it to [verify webhook signatures](/webhooks/verification). Do not print it in shared logs. ## Verify Each Result Confirm every step before continuing. The account API endpoint should return a successful status code. Its response shows the plan, credit balance, and monitor billing values. A `401` means the API key is missing, invalid, or revoked. Next, inspect the monitor response. Confirm its `id`, `username`, `eventTypes`, and `isActive` values. The username must match the account you intend to watch. The event types must match the tweets or replies you need. Finally, inspect the webhook response. Confirm its `id`, `url`, and `eventTypes`. Store the returned `secret` immediately. Xquik returns that secret only during creation. Use the test endpoint before relying on live webhook events. Verify the HMAC signature before parsing a delivery. Return `2xx` after accepting the event. Inspect failed HTTP requests using their status and response body. Never log secret-bearing HTTP headers. Use the troubleshooting steps below for authentication, payment, webhook, or rate-limit failures. ## Next steps Browse tweet, profile, follower, timeline, monitor, and webhook endpoints. Export tweets, replies, followers, following, likes, lists, and media. Execute a giveaway draw on a tweet. Verify HMAC signatures on incoming webhooks. Connect API MCP v2.6.0 through Streamable HTTP. Use `explore`, then `xquik`. Handle errors, rate limits, and retries. Track credits, monitor costs, subscriptions, and rate-limit quotas. ## Troubleshooting Keep using your Xquik account email. Wait 60 seconds, request a new link, then open the newest email within 15 minutes. Check your spam or junk folder. If no email arrives, contact [support@xquik.com](mailto:support@xquik.com) with the exact request time. Verify the header name is `x-api-key` (lowercase). Check that the key starts with `xq_` and hasn't been revoked. Regenerate from the [API Keys dashboard](https://dashboard.xquik.com/en/account?tab=api-keys). Neither status creates checkout. Anonymous non-MPP paid reads return `401` with a Bearer challenge and guest wallet action. The 7 direct MPP reads return `402` with a Payment challenge and the same action. A `402` response lists choices for account or guest credit failures. Ask the user to choose and confirm before creating checkout. See [Billing](/guides/billing). Verify the URL uses HTTPS. Check that the webhook is active via [List Webhooks](/api-reference/webhooks/list). Test delivery with the [Test Webhook](/api-reference/webhooks/test) endpoint. The fixed windows allow 300 reads/1s, 120 writes/60s, and 60 deletes/60s. Respect `Retry-After`. See [Rate Limits](/guides/rate-limits).