Xquik API Integration
Xquik is an X (Twitter) real-time data platform providing a REST API, HMAC webhooks, and an MCP server for AI agents. It covers account monitoring, bulk data extraction (20 tools), giveaway draws, tweet/user lookups, media downloads, follow checks, trending topics, write actions (tweet, like, retweet, follow, DM, profile, media upload, communities), and Telegram integrations.
Quick Reference
|
|
| Base URL |
https://xquik.com/api/v1 |
| Auth |
x-api-key: xq_... header (64 hex chars after xq_ prefix) |
| MCP endpoint |
https://xquik.com/mcp (StreamableHTTP, same API key) |
| Rate limits |
10 req/s sustained, 20 burst (API); 60 req/s sustained, 100 burst (general) |
| Pricing |
$20/month base (1 monitor included), $5/month per extra monitor |
| Quota |
Monthly usage cap. 402 when exhausted. Enable extra usage from dashboard for overage (tiered spending limits: $5/$7/$10/$15/$25) |
| Docs |
docs.xquik.com |
| HTTPS only |
Plain HTTP gets 301 redirect |
Authentication
Every request requires an API key via the x-api-key header. Keys start with xq_ and are generated from the Xquik dashboard. The key is shown only once at creation; store it securely.
const API_KEY = "xq_YOUR_KEY_HERE";
const BASE = "https://xquik.com/api/v1";
const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" };
For Python examples, see references/python-examples.md.
Choosing the Right Endpoint
| Goal |
Endpoint |
Notes |
| Get a single tweet by ID/URL |
GET /x/tweets/{id} |
Full metrics: likes, retweets, views, bookmarks, author info |
| Search tweets by keyword/hashtag |
GET /x/tweets/search?q=... |
Tweet info with optional engagement metrics (likeCount, retweetCount, replyCount) |
| Get a user profile |
GET /x/users/{username} |
Name, bio, follower/following counts, profile picture, location, created date, statuses count |
| Check follow relationship |
GET /x/followers/check?source=A&target=B |
Both directions |
| Get trending topics |
GET /trends?woeid=1 |
Regional trends by WOEID. Metered |
| Get radar (trending news) |
GET /radar?source=hacker_news |
Free, 7 sources: Google Trends, Hacker News, Polymarket, TrustMRR, Wikipedia, GitHub, Reddit |
| Monitor an X account |
POST /monitors |
Track tweets, replies, quotes, retweets, follower changes |
| Update monitor event types |
PATCH /monitors/{id} |
Change subscribed events or pause/resume |
| Poll for events |
GET /events |
Cursor-paginated, filter by monitorId/eventType |
| Receive events in real time |
POST /webhooks |
HMAC-signed delivery to your HTTPS endpoint |
| Update webhook |
PATCH /webhooks/{id} |
Change URL, event types, or pause/resume |
| Run a giveaway draw |
POST /draws |
Pick random winners from tweet replies |
| Download tweet media |
POST /x/media/download |
Single (tweetInput) or bulk (tweetIds[], up to 50). Returns gallery URL. First download metered, cached free |
| Extract bulk data |
POST /extractions |
20 tool types, always estimate cost first |
| Check account/usage |
GET /account |
Plan status, monitors, usage percent |
| Link your X identity |
PUT /account/x-identity |
Required for own-account detection in style analysis |
| Analyze tweet style |
POST /styles |
Cache recent tweets for style reference |
| Save custom style |
PUT /styles/{username} |
Save custom style from tweet texts (free) |
| Get cached style |
GET /styles/{username} |
Retrieve previously cached tweet style |
| Compare styles |
GET /styles/compare?username1=A&username2=B |
Side-by-side comparison of two cached styles |
| Get tweet performance |
GET /styles/{username}/performance |
Live engagement metrics for cached tweets |
| Save a tweet draft |
POST /drafts |
Store drafts for later |
| List/manage drafts |
GET /drafts, DELETE /drafts/{id} |
Retrieve and delete saved drafts |
| Compose a tweet |
POST /compose |
3-step workflow (compose, refine, score). Free, algorithm-backed |
| Connect an X account |
POST /x/accounts |
Credentials encrypted at rest. Required for write actions |
| List connected accounts |
GET /x/accounts |
Free |
| Re-authenticate account |
POST /x/accounts/{id}/reauth |
When session expires |
| Post a tweet |
POST /x/tweets |
From a connected account. Supports replies, media, note tweets, communities |
| Delete a tweet |
DELETE /x/tweets/{id} |
Must own the tweet via connected account |
| Like / Unlike a tweet |
POST / DELETE /x/tweets/{id}/like |
Metered |
| Retweet |
POST /x/tweets/{id}/retweet |
Metered |
| Follow / Unfollow a user |
POST / DELETE /x/users/{id}/follow |
Metered |
| Send a DM |
POST /x/dm/{userId} |
Text, media, reply to message |
| Update profile |
PATCH /x/profile |
Name, bio, location, URL |
| Upload media |
POST /x/media |
FormData. Returns media ID for tweet attachment |
| Community actions |
POST /x/communities, POST /x/communities/{id}/join |
Create, delete, join, leave |
| Create Telegram integration |
POST /integrations |
Receive monitor events in Telegram. Free |
| Manage integrations |
GET /integrations, PATCH /integrations/{id} |
List, update, delete, test, deliveries. Free |
See references/mcp-tools.md for tool selection rules, common mistakes, and unsupported operations.
Error Handling & Retry
All errors return { "error": "error_code" }. Key error codes:
| Status |
Code |
Action |
| 400 |
invalid_input, invalid_id, invalid_params, invalid_tweet_url, invalid_tweet_id, invalid_username, invalid_tool_type, invalid_format, missing_query, missing_params, webhook_inactive, no_media |
Fix the request, do not retry |
| 401 |
unauthenticated |
Check API key |
| 402 |
no_subscription, subscription_inactive, usage_limit_reached, no_addon, extra_usage_disabled, extra_usage_requires_v2, frozen, overage_limit_reached |
Subscribe, enable extra usage, or wait for quota reset |
| 403 |
monitor_limit_reached, api_key_limit_reached |
Delete a monitor/key or add capacity |
| 404 |
not_found, user_not_found, tweet_not_found, style_not_found, draft_not_found, account_not_found |
Resource doesn't exist or belongs to another account |
| 409 |
monitor_already_exists, account_already_connected, already_member |
Resource already exists, use the existing one |
| 422 |
connection_failed, reauth_failed |
X credential verification failed. Check credentials |
| 429 |
x_api_rate_limited |
Rate limited. Retry with exponential backoff, respect Retry-After header |
| 500 |
internal_error |
Retry with backoff |
| 502 |
stream_registration_failed, x_api_unavailable, x_api_unauthorized, x_write_failed, upstream_error, delivery_failed |
Retry with backoff |
Retry only 429 and 5xx. Never retry 4xx (except 429). Max 3 retries with exponential backoff:
async function xquikFetch(path, options = {}) {
const baseDelay = 1000;
for (let attempt = 0; attempt <= 3; attempt++) {
const response = await fetch(`${BASE}${path}`, {
...options,
headers: { ...headers, ...options.headers },
});
if (response.ok) return response.json();
const retryable = response.status === 429 || response.status >= 500;
if (!retryable || attempt === 3) {
const error = await response.json();
throw new Error(`Xquik API ${response.status}: ${error.error}`);
}
const retryAfter = response.headers.get("Retry-After");
const delay = retryAfter
? parseInt(retryAfter, 10) * 1000
: baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
Cursor Pagination
Events, draws, extractions, and extraction results use cursor-based pagination. When more results exist, the response includes hasMore: true and a nextCursor string. Pass nextCursor as the after query parameter.
async function fetchAllPages(path, dataKey) {
const results = [];
let cursor;
while (true) {
const params = new URLSearchParams({ limit: "100" });
if (cursor) params.set("after", cursor);
const data = await xquikFetch(`${path}?${params}`);
results.push(...data[dataKey]);
if (!data.hasMore) break;
cursor = data.nextCursor;
}
return results;
}
Cursors are opaque strings. Never decode or construct them manually.
Extraction Tools (20 Types)
Extractions run bulk data collection jobs. The complete workflow: estimate cost, create job, retrieve results, optionally export.
Tool Types and Required Parameters
| Tool Type |
Required Field |
Description |
reply_extractor |
targetTweetId |
Users who replied to a tweet |
repost_extractor |
targetTweetId |
Users who retweeted a tweet |
quote_extractor |
targetTweetId |
Users who quote-tweeted a tweet |
thread_extractor |
targetTweetId |
All tweets in a thread |
article_extractor |
targetTweetId |
Article content linked in a tweet |
follower_explorer |
targetUsername |
Followers of an account |
following_explorer |
targetUsername |
Accounts followed by a user |
verified_follower_explorer |
targetUsername |
Verified followers of an account |
mention_extractor |
targetUsername |
Tweets mentioning an account |
post_extractor |
targetUsername |
Posts from an account |
community_extractor |
targetCommunityId |
Members of a community |
community_moderator_explorer |
targetCommunityId |
Moderators of a community |
community_post_extractor |
targetCommunityId |
Posts from a community |
community_search |
targetCommunityId + searchQuery |
Search posts within a community |
list_member_extractor |
targetListId |
Members of a list |
list_post_extractor |
targetListId |
Posts from a list |
list_follower_explorer |
targetListId |
Followers of a list |
space_explorer |
targetSpaceId |
Participants of a Space |
people_search |
searchQuery |
Search for users by keyword |
tweet_search_extractor |
searchQuery |
Search and extract tweets by keyword or hashtag (bulk, up to 1,000) |
Complete Extraction Workflow
// Step 1: Estimate cost before running (pass resultsLimit if you only need a sample)
const estimate = await xquikFetch("/extractions/estimate", {
method: "POST",
body: JSON.stringify({
toolType: "follower_explorer",
targetUsername: "elonmusk",
resultsLimit: 1000, // optional: limit to 1,000 results instead of all
}),
});
// Response: { allowed: true, estimatedResults: 195000000, usagePercent: 12, projectedPercent: 98 }
if (!estimate.allowed) {
console.log("Extraction would exceed monthly quota");
return;
}
// Step 2: Create extraction job (pass same resultsLimit to match estimate)
const job = await xquikFetch("/extractions", {
method: "POST",
body: JSON.stringify({
toolType: "follower_explorer",
targetUsername: "elonmusk",
resultsLimit: 1000,
}),
});
// Response: { id: "77777", toolType: "follower_explorer", status: "completed", totalResults: 195000 }
// Step 3: Poll until complete (large jobs may return status "running")
while (job.status === "pending" || job.status === "running") {
await new Promise((r) => setTimeout(r, 2000));
job = await xquikFetch(`/extractions/${job.id}`);
}
// Step 4: Retrieve paginated results (up to 1,000 per page)
let cursor;
const allResults = [];
while (true) {
const path = `/extractions/${job.id}${cursor ? `?after=${cursor}` : ""}`;
const page = await xquikFetch(path);
allResults.push(...page.results);
// Each result: { xUserId, xUsername, xDisplayName, xFollowersCount, xVerified, xProfileImageUrl }
if (!page.hasMore) break;
cursor = page.nextCursor;
}
// Step 5: Export as CSV/XLSX/Markdown (50,000 row limit)
const exportUrl = `${BASE}/extractions/${job.id}/export?format=csv`;
const csvResponse = await fetch(exportUrl, { headers });
const csvData = await csvResponse.text();
Orchestrating Multiple Extractions
When building applications that combine multiple extraction tools (e.g., market research), run them sequentially and respect rate limits:
async function marketResearchPipeline(username) {
// 1. Get user profile
const user = await xquikFetch(`/x/users/${username}`);
// 2. Extract their recent posts
const postsJob = await xquikFetch("/extractions", {
method: "POST",
body: JSON.stringify({ toolType: "post_extractor", targetUsername: username }),
});
// 3. Search for related conversations
const tweets = await xquikFetch(`/x/tweets/search?q=from:${username}`);
// 4. For top tweets, extract replies for sentiment analysis
for (const tweet of tweets.tweets.slice(0, 5)) {
const estimate = await xquikFetch("/extractions/estimate", {
method: "POST",
body: JSON.stringify({ toolType: "reply_extractor", targetTweetId: tweet.id }),
});
if (estimate.allowed) {
const repliesJob = await xquikFetch("/extractions", {
method: "POST",
body: JSON.stringify({ toolType: "reply_extractor", targetTweetId: tweet.id }),
});
// Process replies...
}
}
// 5. Get trending topics for context
const trends = await xquikFetch("/trends?woeid=1");
return { user, posts: postsJob, tweets, trends };
}
Giveaway Draws
Run transparent, auditable giveaway draws from tweet replies with configurable filters.
Create Draw Request
POST /draws with a tweetUrl (required) and optional filters:
| Field |
Type |
Description |
tweetUrl |
string |
Required. Full tweet URL: https://x.com/user/status/ID |
winnerCount |
number |
Winners to select (default 1) |
backupCount |
number |
Backup winners to select |
uniqueAuthorsOnly |
boolean |
Count only one entry per author |
mustRetweet |
boolean |
Require participants to have retweeted |
mustFollowUsername |
string |
Username participants must follow |
filterMinFollowers |
number |
Minimum follower count |
filterAccountAgeDays |
number |
Minimum account age in days |
filterLanguage |
string |
Language code (e.g., "en") |
requiredKeywords |
string[] |
Words that must appear in the reply |
requiredHashtags |
string[] |
Hashtags that must appear (e.g., ["#giveaway"]) |
requiredMentions |
string[] |
Usernames that must be mentioned (e.g., ["@xquik"]) |
Complete Draw Workflow
// Step 1: Create draw with filters
const draw = await xquikFetch("/draws", {
method: "POST",
body: JSON.stringify({
tweetUrl: "https://x.com/burakbayir/status/1893456789012345678",
winnerCount: 3,
backupCount: 2,
uniqueAuthorsOnly: true,
mustRetweet: true,
mustFollowUsername: "burakbayir",
filterMinFollowers: 50,
filterAccountAgeDays: 30,
filterLanguage: "en",
requiredHashtags: ["#giveaway"],
}),
});
// Response:
// {
// id: "42",
// tweetId: "1893456789012345678",
// tweetUrl: "https://x.com/burakbayir/status/1893456789012345678",
// tweetText: "Giveaway! RT + Follow to enter...",
// tweetAuthorUsername: "burakbayir",
// tweetLikeCount: 5200,
// tweetRetweetCount: 3100,
// tweetReplyCount: 890,
// tweetQuoteCount: 45,
// status: "completed",
// totalEntries: 890,
// validEntries: 312,
// createdAt: "2026-02-24T10:00:00.000Z",
// drawnAt: "2026-02-24T10:01:00.000Z"
// }
// Step 2: Get draw details with winners
const details = await xquikFetch(`/draws/${draw.id}`);
// details.winners: [
// { position: 1, authorUsername: "winner1", tweetId: "...", isBackup: false },
// { position: 2, authorUsername: "winner2", tweetId: "...", isBackup: false },
// { position: 3, authorUsername: "winner3", tweetId: "...", isBackup: false },
// { position: 4, authorUsername: "backup1", tweetId: "...", isBackup: true },
// { position: 5, authorUsername: "backup2", tweetId: "...", isBackup: true },
// ]
// Step 3: Export results
const exportUrl = `${BASE}/draws/${draw.id}/export?format=csv`;
Webhook Event Handling
Webhooks deliver events to your HTTPS endpoint with HMAC-SHA256 signatures. Each delivery is a POST with X-Xquik-Signature header and JSON body containing eventType, username, and data.
Webhook Handler (Express)
import express from "express";
import { createHmac, timingSafeEqual, createHash } from "node:crypto";
const WEBHOOK_SECRET = process.env.XQUIK_WEBHOOK_SECRET;
const processedHashes = new Set(); // Use Redis/DB in production
function verifySignature(payload, signature, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(payload).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
const app = express();
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-xquik-signature"];
const payload = req.body.toString();
// 1. Verify HMAC signature (constant-time comparison)
if (!signature || !verifySignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// 2. Deduplicate (retries can deliver the same event twice)
const payloadHash = createHash("sha256").update(payload).digest("hex");
if (processedHashes.has(payloadHash)) {
return res.status(200).send("Already processed");
}
processedHashes.add(payloadHash);
// 3. Parse and route by event type
const event = JSON.parse(payload);
// event.eventType: "tweet.new" | "tweet.reply" | "tweet.quote" | "tweet.retweet" | "follower.gained" | "follower.lost"
// event.username: monitored account username
// event.data: tweet data ({ tweetId, text, metrics }) or follower data ({ followerId, followerUsername, followerName, followerFollowersCount, followerVerified })
// 4. Respond within 10 seconds (process async if slow)
res.status(200).send("OK");
});
app.listen(3000);
For Flask (Python) webhook handler, see references/python-examples.md.
Webhook security rules:
- Always verify signature before processing (constant-time comparison)
- Compute HMAC over raw body bytes, not re-serialized JSON
- Respond
200 within 10 seconds; queue slow processing for async
- Deduplicate by payload hash (retries can deliver same event twice)
- Store webhook secret in environment variables, never hardcode
- Retry policy: 5 attempts with exponential backoff on failure
Check delivery status via GET /webhooks/{id}/deliveries to monitor successful and failed attempts.
Real-Time Monitoring Setup
Complete end-to-end: create monitor, register webhook, handle events.
// 1. Create monitor
const monitor = await xquikFetch("/monitors", {
method: "POST",
body: JSON.stringify({
username: "elonmusk",
eventTypes: ["tweet.new", "tweet.reply", "tweet.quote", "follower.gained"],
}),
});
// Response: { id: "7", username: "elonmusk", xUserId: "44196397", eventTypes: [...], createdAt: "..." }
// 2. Register webhook
const webhook = await xquikFetch("/webhooks", {
method: "POST",
body: JSON.stringify({
url: "https://your-server.com/webhook",
eventTypes: ["tweet.new", "tweet.reply"],
}),
});
// IMPORTANT: Save webhook.secret. It is shown only once!
// 3. Poll events (alternative to webhooks)
const events = await xquikFetch("/events?monitorId=7&limit=50");
// Response: { events: [...], hasMore: false }
Event types: tweet.new, tweet.quote, tweet.reply, tweet.retweet, follower.gained, follower.lost.
MCP Server (AI Agents)
The MCP server at https://xquik.com/mcp uses a code-execution sandbox model with 2 tools (explore + xquik). The agent writes async JavaScript arrow functions that run in a sandboxed environment with auth injected automatically. StreamableHTTP transport. API key auth (x-api-key header) for CLI/IDE clients; OAuth 2.1 for web clients (Claude.ai, ChatGPT Developer Mode). Supported platforms: Claude.ai, Claude Desktop, Claude Code, ChatGPT (Custom GPT, Agents SDK, Developer Mode), Codex CLI, Cursor, VS Code, Windsurf, OpenCode.
Legacy v1 server at https://xquik.com/mcp/v1 exposes 18 discrete tools with traditional input schemas. All new integrations should use the default v2 server at /mcp.
The server also registers 5 guided workflow prompts: compose-tweet, compose-trending-tweet, compose-radar-tweet, analyze-account, run-giveaway. Use prompts/list and prompts/get in compatible clients.
For setup configs per platform, read references/mcp-setup.md. For the complete v1 tool reference with input/output schemas, annotations, and selection rules, read references/mcp-tools.md.
MCP vs REST API
|
MCP Server (v2) |
REST API |
| Best for |
AI agents, IDE integrations |
Custom apps, scripts, backend services |
| Model |
2 tools (explore + xquik) with code-execution sandbox |
76 individual endpoints |
| Categories |
9: account, composition, extraction, integrations, media, monitoring, twitter, x-accounts, x-write |
Same |
| User profile |
Full (via xquik tool calling REST endpoints) |
Full profile |
| Search results |
Full (via xquik tool) |
Includes optional engagement metrics |
| Webhook/monitor update |
Full PATCH via xquik tool |
PATCH endpoints |
| Write actions |
Full via xquik tool (tweet, like, follow, DM, etc.) |
POST/DELETE endpoints |
| File export |
Not available |
CSV, XLSX, Markdown |
| Unique to REST |
- |
API key management, file export (CSV/XLSX/MD), account locale update |
Use the REST API GET /x/users/{username} for the complete user profile with verified, location, createdAt, and statusesCount fields.
Workflow Patterns
Common multi-step tool sequences:
- Set up real-time alerts:
monitors (action=add) -> webhooks (action=add) -> webhooks (action=test)
- Run a giveaway:
get-account (check budget) -> draws (action=run)
- Bulk extraction:
get-account (check subscription) -> extractions (action=estimate) -> extractions (action=run) -> extractions (action=get, results)
- Full tweet analysis:
lookup-tweet (metrics) -> extractions (action=run) with thread_extractor (full thread)
- Find and analyze user:
get-user-info (profile) -> search-tweets from:username (recent tweets) -> lookup-tweet (metrics on specific tweet)
- Compose algorithm-optimized tweet:
compose-tweet (step=compose) -> AI asks follow-ups -> compose-tweet (step=refine) -> AI drafts tweet -> compose-tweet (step=score) -> iterate
- Analyze tweet style:
styles (action=analyze, fetch & cache tweets) -> styles (action=get, reference) -> compose-tweet with styleUsername
- Compare styles:
styles (action=analyze) for both accounts -> styles (action=compare)
- Track tweet performance:
styles (action=analyze, cache tweets) -> styles (action=analyze-performance, live metrics)
- Save & manage drafts:
compose-tweet -> drafts (action=save) -> drafts (action=list) -> drafts (action=get/delete)
- Download & share media:
download-media (returns permanent hosted URLs)
- Get trending news:
get-radar (7 sources, free) -> compose-tweet with trending topic
- Subscribe or manage billing:
subscribe (returns Stripe URL)
- Post a tweet: connect X account ->
POST /x/tweets with account + text (optionally attach media via POST /x/media first)
- Engage with tweets:
POST /x/tweets/{id}/like, POST /x/tweets/{id}/retweet, POST /x/users/{id}/follow
- Set up Telegram alerts:
POST /integrations (type=telegram, chatId, eventTypes) -> POST /integrations/{id}/test
Pricing & Quota
- Base plan: $20/month (1 monitor, monthly usage quota)
- Extra monitors: $5/month each
- Per-operation costs: tweet search $0.003, user profile $0.0036, follower fetch $0.003, verified follower fetch $0.006, follow check $0.02, media download $0.003, article extraction $0.02
- Free: account info, monitor/webhook management, radar, extraction history, cost estimates, tweet composition (compose, refine, score), style cache management (list, get, save, delete, compare), drafts, X account management (connect, list, disconnect, reauth), integration management (create, list, update, delete, test)
- Metered: tweet search, user lookup, tweet lookup, follow check, media download (first download only, cached free), extractions, draws, style analysis, performance analysis, trends, write actions (tweet, like, retweet, follow, DM, profile, media upload, communities)
- Extra usage: enable from dashboard to continue metered calls beyond included allowance. Tiered spending limits: $5 -> $7 -> $10 -> $15 -> $25 (increases with each paid overage invoice)
- Quota enforcement:
402 usage_limit_reached when included allowance exhausted (or 402 overage_limit_reached if extra usage is active and spending limit reached)
- Check usage:
GET /account returns usagePercent (0-100)
Conventions
- IDs are strings. Bigint values; treat as opaque strings, never parse as numbers
- Timestamps are ISO 8601 UTC. Example:
2026-02-24T10:30:00.000Z
- Errors return JSON. Format:
{ "error": "error_code" }
- Cursors are opaque. Pass
nextCursor as the after query parameter, never decode
- Export formats:
csv, xlsx, md via GET /extractions/{id}/export?format=csv or GET /draws/{id}/export?format=csv&type=winners
Reference Files
For additional detail beyond this guide:
references/mcp-tools.md: All 18 legacy v1 MCP tools with input/output schemas, annotations, selection rules, workflow patterns, common mistakes, and unsupported operations
references/api-endpoints.md: All REST API endpoints with methods, paths, parameters, and response shapes
references/python-examples.md: Python equivalents of all JavaScript examples (retry, extraction, draw, webhook)
references/webhooks.md: Extended webhook examples, local testing with ngrok, delivery status monitoring
references/mcp-setup.md: MCP server configuration for 10 IDEs and AI agent platforms
references/extractions.md: Extraction tool details, export columns
references/types.md: TypeScript type definitions for all REST API and MCP output objects
1---2name: x-twitter-scraper3description: X API & Twitter scraper skill for AI coding agents. Builds integrations with the Xquik REST API, MCP server & webhooks: tweet search, user lookup, follower extraction, engagement metrics, giveaway contest draws, trending topics, account monitoring, reply/retweet/quote extraction, community & Space data, mutual follow checks, write actions (tweet, like, retweet, follow, DM, profile, media upload, communities), Telegram integrations. Works with Claude Code, Cursor, Codex, Copilot, Windsurf & 40+ agents.4license: MIT5---67# Xquik API Integration89Xquik is an X (Twitter) real-time data platform providing a REST API, HMAC webhooks, and an MCP server for AI agents. It covers account monitoring, bulk data extraction (20 tools), giveaway draws, tweet/user lookups, media downloads, follow checks, trending topics, write actions (tweet, like, retweet, follow, DM, profile, media upload, communities), and Telegram integrations.1011## Quick Reference1213| | |14|---|---|15| **Base URL** | `https://xquik.com/api/v1` |16| **Auth** | `x-api-key: xq_...` header (64 hex chars after `xq_` prefix) |17| **MCP endpoint** | `https://xquik.com/mcp` (StreamableHTTP, same API key) |18| **Rate limits** | 10 req/s sustained, 20 burst (API); 60 req/s sustained, 100 burst (general) |19| **Pricing** | $20/month base (1 monitor included), $5/month per extra monitor |20| **Quota** | Monthly usage cap. `402` when exhausted. Enable extra usage from dashboard for overage (tiered spending limits: $5/$7/$10/$15/$25) |21| **Docs** | [docs.xquik.com](https://docs.xquik.com) |22| **HTTPS only** | Plain HTTP gets `301` redirect |2324## Authentication2526Every request requires an API key via the `x-api-key` header. Keys start with `xq_` and are generated from the Xquik dashboard. The key is shown only once at creation; store it securely.2728```javascript29const API_KEY = "xq_YOUR_KEY_HERE";30const BASE = "https://xquik.com/api/v1";31const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" };32```3334For Python examples, see [references/python-examples.md](references/python-examples.md).3536## Choosing the Right Endpoint3738| Goal | Endpoint | Notes |39|------|----------|-------|40| **Get a single tweet** by ID/URL | `GET /x/tweets/{id}` | Full metrics: likes, retweets, views, bookmarks, author info |41| **Search tweets** by keyword/hashtag | `GET /x/tweets/search?q=...` | Tweet info with optional engagement metrics (likeCount, retweetCount, replyCount) |42| **Get a user profile** | `GET /x/users/{username}` | Name, bio, follower/following counts, profile picture, location, created date, statuses count |43| **Check follow relationship** | `GET /x/followers/check?source=A&target=B` | Both directions |44| **Get trending topics** | `GET /trends?woeid=1` | Regional trends by WOEID. Metered |45| **Get radar (trending news)** | `GET /radar?source=hacker_news` | Free, 7 sources: Google Trends, Hacker News, Polymarket, TrustMRR, Wikipedia, GitHub, Reddit |46| **Monitor an X account** | `POST /monitors` | Track tweets, replies, quotes, retweets, follower changes |47| **Update monitor event types** | `PATCH /monitors/{id}` | Change subscribed events or pause/resume |48| **Poll for events** | `GET /events` | Cursor-paginated, filter by monitorId/eventType |49| **Receive events in real time** | `POST /webhooks` | HMAC-signed delivery to your HTTPS endpoint |50| **Update webhook** | `PATCH /webhooks/{id}` | Change URL, event types, or pause/resume |51| **Run a giveaway draw** | `POST /draws` | Pick random winners from tweet replies |52| **Download tweet media** | `POST /x/media/download` | Single (`tweetInput`) or bulk (`tweetIds[]`, up to 50). Returns gallery URL. First download metered, cached free |53| **Extract bulk data** | `POST /extractions` | 20 tool types, always estimate cost first |54| **Check account/usage** | `GET /account` | Plan status, monitors, usage percent |55| **Link your X identity** | `PUT /account/x-identity` | Required for own-account detection in style analysis |56| **Analyze tweet style** | `POST /styles` | Cache recent tweets for style reference |57| **Save custom style** | `PUT /styles/{username}` | Save custom style from tweet texts (free) |58| **Get cached style** | `GET /styles/{username}` | Retrieve previously cached tweet style |59| **Compare styles** | `GET /styles/compare?username1=A&username2=B` | Side-by-side comparison of two cached styles |60| **Get tweet performance** | `GET /styles/{username}/performance` | Live engagement metrics for cached tweets |61| **Save a tweet draft** | `POST /drafts` | Store drafts for later |62| **List/manage drafts** | `GET /drafts`, `DELETE /drafts/{id}` | Retrieve and delete saved drafts |63| **Compose a tweet** | `POST /compose` | 3-step workflow (compose, refine, score). Free, algorithm-backed |64| **Connect an X account** | `POST /x/accounts` | Credentials encrypted at rest. Required for write actions |65| **List connected accounts** | `GET /x/accounts` | Free |66| **Re-authenticate account** | `POST /x/accounts/{id}/reauth` | When session expires |67| **Post a tweet** | `POST /x/tweets` | From a connected account. Supports replies, media, note tweets, communities |68| **Delete a tweet** | `DELETE /x/tweets/{id}` | Must own the tweet via connected account |69| **Like / Unlike a tweet** | `POST` / `DELETE /x/tweets/{id}/like` | Metered |70| **Retweet** | `POST /x/tweets/{id}/retweet` | Metered |71| **Follow / Unfollow a user** | `POST` / `DELETE /x/users/{id}/follow` | Metered |72| **Send a DM** | `POST /x/dm/{userId}` | Text, media, reply to message |73| **Update profile** | `PATCH /x/profile` | Name, bio, location, URL |74| **Upload media** | `POST /x/media` | FormData. Returns media ID for tweet attachment |75| **Community actions** | `POST /x/communities`, `POST /x/communities/{id}/join` | Create, delete, join, leave |76| **Create Telegram integration** | `POST /integrations` | Receive monitor events in Telegram. Free |77| **Manage integrations** | `GET /integrations`, `PATCH /integrations/{id}` | List, update, delete, test, deliveries. Free |7879See [references/mcp-tools.md](references/mcp-tools.md) for tool selection rules, common mistakes, and unsupported operations.8081## Error Handling & Retry8283All errors return `{ "error": "error_code" }`. Key error codes:8485| Status | Code | Action |86|--------|------|--------|87| 400 | `invalid_input`, `invalid_id`, `invalid_params`, `invalid_tweet_url`, `invalid_tweet_id`, `invalid_username`, `invalid_tool_type`, `invalid_format`, `missing_query`, `missing_params`, `webhook_inactive`, `no_media` | Fix the request, do not retry |88| 401 | `unauthenticated` | Check API key |89| 402 | `no_subscription`, `subscription_inactive`, `usage_limit_reached`, `no_addon`, `extra_usage_disabled`, `extra_usage_requires_v2`, `frozen`, `overage_limit_reached` | Subscribe, enable extra usage, or wait for quota reset |90| 403 | `monitor_limit_reached`, `api_key_limit_reached` | Delete a monitor/key or add capacity |91| 404 | `not_found`, `user_not_found`, `tweet_not_found`, `style_not_found`, `draft_not_found`, `account_not_found` | Resource doesn't exist or belongs to another account |92| 409 | `monitor_already_exists`, `account_already_connected`, `already_member` | Resource already exists, use the existing one |93| 422 | `connection_failed`, `reauth_failed` | X credential verification failed. Check credentials |94| 429 | `x_api_rate_limited` | Rate limited. Retry with exponential backoff, respect `Retry-After` header |95| 500 | `internal_error` | Retry with backoff |96| 502 | `stream_registration_failed`, `x_api_unavailable`, `x_api_unauthorized`, `x_write_failed`, `upstream_error`, `delivery_failed` | Retry with backoff |9798Retry only `429` and `5xx`. Never retry `4xx` (except 429). Max 3 retries with exponential backoff:99100```javascript101async function xquikFetch(path, options = {}) {102 const baseDelay = 1000;103104 for (let attempt = 0; attempt <= 3; attempt++) {105 const response = await fetch(`${BASE}${path}`, {106 ...options,107 headers: { ...headers, ...options.headers },108 });109110 if (response.ok) return response.json();111112 const retryable = response.status === 429 || response.status >= 500;113 if (!retryable || attempt === 3) {114 const error = await response.json();115 throw new Error(`Xquik API ${response.status}: ${error.error}`);116 }117118 const retryAfter = response.headers.get("Retry-After");119 const delay = retryAfter120 ? parseInt(retryAfter, 10) * 1000121 : baseDelay * Math.pow(2, attempt) + Math.random() * 1000;122123 await new Promise((resolve) => setTimeout(resolve, delay));124 }125}126```127128## Cursor Pagination129130Events, draws, extractions, and extraction results use cursor-based pagination. When more results exist, the response includes `hasMore: true` and a `nextCursor` string. Pass `nextCursor` as the `after` query parameter.131132```javascript133async function fetchAllPages(path, dataKey) {134 const results = [];135 let cursor;136137 while (true) {138 const params = new URLSearchParams({ limit: "100" });139 if (cursor) params.set("after", cursor);140141 const data = await xquikFetch(`${path}?${params}`);142 results.push(...data[dataKey]);143144 if (!data.hasMore) break;145 cursor = data.nextCursor;146 }147148 return results;149}150```151152Cursors are opaque strings. Never decode or construct them manually.153154## Extraction Tools (20 Types)155156Extractions run bulk data collection jobs. The complete workflow: estimate cost, create job, retrieve results, optionally export.157158### Tool Types and Required Parameters159160| Tool Type | Required Field | Description |161|-----------|---------------|-------------|162| `reply_extractor` | `targetTweetId` | Users who replied to a tweet |163| `repost_extractor` | `targetTweetId` | Users who retweeted a tweet |164| `quote_extractor` | `targetTweetId` | Users who quote-tweeted a tweet |165| `thread_extractor` | `targetTweetId` | All tweets in a thread |166| `article_extractor` | `targetTweetId` | Article content linked in a tweet |167| `follower_explorer` | `targetUsername` | Followers of an account |168| `following_explorer` | `targetUsername` | Accounts followed by a user |169| `verified_follower_explorer` | `targetUsername` | Verified followers of an account |170| `mention_extractor` | `targetUsername` | Tweets mentioning an account |171| `post_extractor` | `targetUsername` | Posts from an account |172| `community_extractor` | `targetCommunityId` | Members of a community |173| `community_moderator_explorer` | `targetCommunityId` | Moderators of a community |174| `community_post_extractor` | `targetCommunityId` | Posts from a community |175| `community_search` | `targetCommunityId` + `searchQuery` | Search posts within a community |176| `list_member_extractor` | `targetListId` | Members of a list |177| `list_post_extractor` | `targetListId` | Posts from a list |178| `list_follower_explorer` | `targetListId` | Followers of a list |179| `space_explorer` | `targetSpaceId` | Participants of a Space |180| `people_search` | `searchQuery` | Search for users by keyword |181| `tweet_search_extractor` | `searchQuery` | Search and extract tweets by keyword or hashtag (bulk, up to 1,000) |182183### Complete Extraction Workflow184185```javascript186// Step 1: Estimate cost before running (pass resultsLimit if you only need a sample)187const estimate = await xquikFetch("/extractions/estimate", {188 method: "POST",189 body: JSON.stringify({190 toolType: "follower_explorer",191 targetUsername: "elonmusk",192 resultsLimit: 1000, // optional: limit to 1,000 results instead of all193 }),194});195// Response: { allowed: true, estimatedResults: 195000000, usagePercent: 12, projectedPercent: 98 }196197if (!estimate.allowed) {198 console.log("Extraction would exceed monthly quota");199 return;200}201202// Step 2: Create extraction job (pass same resultsLimit to match estimate)203const job = await xquikFetch("/extractions", {204 method: "POST",205 body: JSON.stringify({206 toolType: "follower_explorer",207 targetUsername: "elonmusk",208 resultsLimit: 1000,209 }),210});211// Response: { id: "77777", toolType: "follower_explorer", status: "completed", totalResults: 195000 }212213// Step 3: Poll until complete (large jobs may return status "running")214while (job.status === "pending" || job.status === "running") {215 await new Promise((r) => setTimeout(r, 2000));216 job = await xquikFetch(`/extractions/${job.id}`);217}218219// Step 4: Retrieve paginated results (up to 1,000 per page)220let cursor;221const allResults = [];222223while (true) {224 const path = `/extractions/${job.id}${cursor ? `?after=${cursor}` : ""}`;225 const page = await xquikFetch(path);226 allResults.push(...page.results);227 // Each result: { xUserId, xUsername, xDisplayName, xFollowersCount, xVerified, xProfileImageUrl }228229 if (!page.hasMore) break;230 cursor = page.nextCursor;231}232233// Step 5: Export as CSV/XLSX/Markdown (50,000 row limit)234const exportUrl = `${BASE}/extractions/${job.id}/export?format=csv`;235const csvResponse = await fetch(exportUrl, { headers });236const csvData = await csvResponse.text();237```238239### Orchestrating Multiple Extractions240241When building applications that combine multiple extraction tools (e.g., market research), run them sequentially and respect rate limits:242243```javascript244async function marketResearchPipeline(username) {245 // 1. Get user profile246 const user = await xquikFetch(`/x/users/${username}`);247248 // 2. Extract their recent posts249 const postsJob = await xquikFetch("/extractions", {250 method: "POST",251 body: JSON.stringify({ toolType: "post_extractor", targetUsername: username }),252 });253254 // 3. Search for related conversations255 const tweets = await xquikFetch(`/x/tweets/search?q=from:${username}`);256257 // 4. For top tweets, extract replies for sentiment analysis258 for (const tweet of tweets.tweets.slice(0, 5)) {259 const estimate = await xquikFetch("/extractions/estimate", {260 method: "POST",261 body: JSON.stringify({ toolType: "reply_extractor", targetTweetId: tweet.id }),262 });263264 if (estimate.allowed) {265 const repliesJob = await xquikFetch("/extractions", {266 method: "POST",267 body: JSON.stringify({ toolType: "reply_extractor", targetTweetId: tweet.id }),268 });269 // Process replies...270 }271 }272273 // 5. Get trending topics for context274 const trends = await xquikFetch("/trends?woeid=1");275276 return { user, posts: postsJob, tweets, trends };277}278```279280## Giveaway Draws281282Run transparent, auditable giveaway draws from tweet replies with configurable filters.283284### Create Draw Request285286`POST /draws` with a `tweetUrl` (required) and optional filters:287288| Field | Type | Description |289|-------|------|-------------|290| `tweetUrl` | string | **Required.** Full tweet URL: `https://x.com/user/status/ID` |291| `winnerCount` | number | Winners to select (default 1) |292| `backupCount` | number | Backup winners to select |293| `uniqueAuthorsOnly` | boolean | Count only one entry per author |294| `mustRetweet` | boolean | Require participants to have retweeted |295| `mustFollowUsername` | string | Username participants must follow |296| `filterMinFollowers` | number | Minimum follower count |297| `filterAccountAgeDays` | number | Minimum account age in days |298| `filterLanguage` | string | Language code (e.g., `"en"`) |299| `requiredKeywords` | string[] | Words that must appear in the reply |300| `requiredHashtags` | string[] | Hashtags that must appear (e.g., `["#giveaway"]`) |301| `requiredMentions` | string[] | Usernames that must be mentioned (e.g., `["@xquik"]`) |302303### Complete Draw Workflow304305```javascript306// Step 1: Create draw with filters307const draw = await xquikFetch("/draws", {308 method: "POST",309 body: JSON.stringify({310 tweetUrl: "https://x.com/burakbayir/status/1893456789012345678",311 winnerCount: 3,312 backupCount: 2,313 uniqueAuthorsOnly: true,314 mustRetweet: true,315 mustFollowUsername: "burakbayir",316 filterMinFollowers: 50,317 filterAccountAgeDays: 30,318 filterLanguage: "en",319 requiredHashtags: ["#giveaway"],320 }),321});322// Response:323// {324// id: "42",325// tweetId: "1893456789012345678",326// tweetUrl: "https://x.com/burakbayir/status/1893456789012345678",327// tweetText: "Giveaway! RT + Follow to enter...",328// tweetAuthorUsername: "burakbayir",329// tweetLikeCount: 5200,330// tweetRetweetCount: 3100,331// tweetReplyCount: 890,332// tweetQuoteCount: 45,333// status: "completed",334// totalEntries: 890,335// validEntries: 312,336// createdAt: "2026-02-24T10:00:00.000Z",337// drawnAt: "2026-02-24T10:01:00.000Z"338// }339340// Step 2: Get draw details with winners341const details = await xquikFetch(`/draws/${draw.id}`);342// details.winners: [343// { position: 1, authorUsername: "winner1", tweetId: "...", isBackup: false },344// { position: 2, authorUsername: "winner2", tweetId: "...", isBackup: false },345// { position: 3, authorUsername: "winner3", tweetId: "...", isBackup: false },346// { position: 4, authorUsername: "backup1", tweetId: "...", isBackup: true },347// { position: 5, authorUsername: "backup2", tweetId: "...", isBackup: true },348// ]349350// Step 3: Export results351const exportUrl = `${BASE}/draws/${draw.id}/export?format=csv`;352```353354## Webhook Event Handling355356Webhooks deliver events to your HTTPS endpoint with HMAC-SHA256 signatures. Each delivery is a POST with `X-Xquik-Signature` header and JSON body containing `eventType`, `username`, and `data`.357358### Webhook Handler (Express)359360```javascript361import express from "express";362import { createHmac, timingSafeEqual, createHash } from "node:crypto";363364const WEBHOOK_SECRET = process.env.XQUIK_WEBHOOK_SECRET;365const processedHashes = new Set(); // Use Redis/DB in production366367function verifySignature(payload, signature, secret) {368 const expected = "sha256=" + createHmac("sha256", secret).update(payload).digest("hex");369 return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));370}371372const app = express();373374app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {375 const signature = req.headers["x-xquik-signature"];376 const payload = req.body.toString();377378 // 1. Verify HMAC signature (constant-time comparison)379 if (!signature || !verifySignature(payload, signature, WEBHOOK_SECRET)) {380 return res.status(401).send("Invalid signature");381 }382383 // 2. Deduplicate (retries can deliver the same event twice)384 const payloadHash = createHash("sha256").update(payload).digest("hex");385 if (processedHashes.has(payloadHash)) {386 return res.status(200).send("Already processed");387 }388 processedHashes.add(payloadHash);389390 // 3. Parse and route by event type391 const event = JSON.parse(payload);392 // event.eventType: "tweet.new" | "tweet.reply" | "tweet.quote" | "tweet.retweet" | "follower.gained" | "follower.lost"393 // event.username: monitored account username394 // event.data: tweet data ({ tweetId, text, metrics }) or follower data ({ followerId, followerUsername, followerName, followerFollowersCount, followerVerified })395396 // 4. Respond within 10 seconds (process async if slow)397 res.status(200).send("OK");398});399400app.listen(3000);401```402403For Flask (Python) webhook handler, see [references/python-examples.md](references/python-examples.md#webhook-handler-flask).404405Webhook security rules:406- Always verify signature before processing (constant-time comparison)407- Compute HMAC over raw body bytes, not re-serialized JSON408- Respond `200` within 10 seconds; queue slow processing for async409- Deduplicate by payload hash (retries can deliver same event twice)410- Store webhook secret in environment variables, never hardcode411- Retry policy: 5 attempts with exponential backoff on failure412413Check delivery status via `GET /webhooks/{id}/deliveries` to monitor successful and failed attempts.414415## Real-Time Monitoring Setup416417Complete end-to-end: create monitor, register webhook, handle events.418419```javascript420// 1. Create monitor421const monitor = await xquikFetch("/monitors", {422 method: "POST",423 body: JSON.stringify({424 username: "elonmusk",425 eventTypes: ["tweet.new", "tweet.reply", "tweet.quote", "follower.gained"],426 }),427});428// Response: { id: "7", username: "elonmusk", xUserId: "44196397", eventTypes: [...], createdAt: "..." }429430// 2. Register webhook431const webhook = await xquikFetch("/webhooks", {432 method: "POST",433 body: JSON.stringify({434 url: "https://your-server.com/webhook",435 eventTypes: ["tweet.new", "tweet.reply"],436 }),437});438// IMPORTANT: Save webhook.secret. It is shown only once!439440// 3. Poll events (alternative to webhooks)441const events = await xquikFetch("/events?monitorId=7&limit=50");442// Response: { events: [...], hasMore: false }443```444445Event types: `tweet.new`, `tweet.quote`, `tweet.reply`, `tweet.retweet`, `follower.gained`, `follower.lost`.446447## MCP Server (AI Agents)448449The MCP server at `https://xquik.com/mcp` uses a code-execution sandbox model with 2 tools (`explore` + `xquik`). The agent writes async JavaScript arrow functions that run in a sandboxed environment with auth injected automatically. StreamableHTTP transport. API key auth (`x-api-key` header) for CLI/IDE clients; OAuth 2.1 for web clients (Claude.ai, ChatGPT Developer Mode). Supported platforms: Claude.ai, Claude Desktop, Claude Code, ChatGPT (Custom GPT, Agents SDK, Developer Mode), Codex CLI, Cursor, VS Code, Windsurf, OpenCode.450451**Legacy v1 server** at `https://xquik.com/mcp/v1` exposes 18 discrete tools with traditional input schemas. All new integrations should use the default v2 server at `/mcp`.452453The server also registers 5 guided workflow prompts: `compose-tweet`, `compose-trending-tweet`, `compose-radar-tweet`, `analyze-account`, `run-giveaway`. Use `prompts/list` and `prompts/get` in compatible clients.454455For setup configs per platform, read [references/mcp-setup.md](references/mcp-setup.md). For the complete v1 tool reference with input/output schemas, annotations, and selection rules, read [references/mcp-tools.md](references/mcp-tools.md).456457### MCP vs REST API458459| | MCP Server (v2) | REST API |460|---|------------|----------|461| **Best for** | AI agents, IDE integrations | Custom apps, scripts, backend services |462| **Model** | 2 tools (`explore` + `xquik`) with code-execution sandbox | 76 individual endpoints |463| **Categories** | 9: account, composition, extraction, integrations, media, monitoring, twitter, x-accounts, x-write | Same |464| **User profile** | Full (via `xquik` tool calling REST endpoints) | Full profile |465| **Search results** | Full (via `xquik` tool) | Includes optional engagement metrics |466| **Webhook/monitor update** | Full PATCH via `xquik` tool | PATCH endpoints |467| **Write actions** | Full via `xquik` tool (tweet, like, follow, DM, etc.) | POST/DELETE endpoints |468| **File export** | Not available | CSV, XLSX, Markdown |469| **Unique to REST** | - | API key management, file export (CSV/XLSX/MD), account locale update |470471Use the REST API `GET /x/users/{username}` for the complete user profile with `verified`, `location`, `createdAt`, and `statusesCount` fields.472473### Workflow Patterns474475Common multi-step tool sequences:476477- **Set up real-time alerts:** `monitors` (action=add) -> `webhooks` (action=add) -> `webhooks` (action=test)478- **Run a giveaway:** `get-account` (check budget) -> `draws` (action=run)479- **Bulk extraction:** `get-account` (check subscription) -> `extractions` (action=estimate) -> `extractions` (action=run) -> `extractions` (action=get, results)480- **Full tweet analysis:** `lookup-tweet` (metrics) -> `extractions` (action=run) with `thread_extractor` (full thread)481- **Find and analyze user:** `get-user-info` (profile) -> `search-tweets from:username` (recent tweets) -> `lookup-tweet` (metrics on specific tweet)482- **Compose algorithm-optimized tweet:** `compose-tweet` (step=compose) -> AI asks follow-ups -> `compose-tweet` (step=refine) -> AI drafts tweet -> `compose-tweet` (step=score) -> iterate483- **Analyze tweet style:** `styles` (action=analyze, fetch & cache tweets) -> `styles` (action=get, reference) -> `compose-tweet` with `styleUsername`484- **Compare styles:** `styles` (action=analyze) for both accounts -> `styles` (action=compare)485- **Track tweet performance:** `styles` (action=analyze, cache tweets) -> `styles` (action=analyze-performance, live metrics)486- **Save & manage drafts:** `compose-tweet` -> `drafts` (action=save) -> `drafts` (action=list) -> `drafts` (action=get/delete)487- **Download & share media:** `download-media` (returns permanent hosted URLs)488- **Get trending news:** `get-radar` (7 sources, free) -> `compose-tweet` with trending topic489- **Subscribe or manage billing:** `subscribe` (returns Stripe URL)490- **Post a tweet:** connect X account -> `POST /x/tweets` with `account` + `text` (optionally attach media via `POST /x/media` first)491- **Engage with tweets:** `POST /x/tweets/{id}/like`, `POST /x/tweets/{id}/retweet`, `POST /x/users/{id}/follow`492- **Set up Telegram alerts:** `POST /integrations` (type=telegram, chatId, eventTypes) -> `POST /integrations/{id}/test`493494## Pricing & Quota495496- **Base plan**: $20/month (1 monitor, monthly usage quota)497- **Extra monitors**: $5/month each498- **Per-operation costs**: tweet search $0.003, user profile $0.0036, follower fetch $0.003, verified follower fetch $0.006, follow check $0.02, media download $0.003, article extraction $0.02499- **Free**: account info, monitor/webhook management, radar, extraction history, cost estimates, tweet composition (compose, refine, score), style cache management (list, get, save, delete, compare), drafts, X account management (connect, list, disconnect, reauth), integration management (create, list, update, delete, test)500- **Metered**: tweet search, user lookup, tweet lookup, follow check, media download (first download only, cached free), extractions, draws, style analysis, performance analysis, trends, write actions (tweet, like, retweet, follow, DM, profile, media upload, communities)501- **Extra usage**: enable from dashboard to continue metered calls beyond included allowance. Tiered spending limits: $5 -> $7 -> $10 -> $15 -> $25 (increases with each paid overage invoice)502- **Quota enforcement**: `402 usage_limit_reached` when included allowance exhausted (or `402 overage_limit_reached` if extra usage is active and spending limit reached)503- **Check usage**: `GET /account` returns `usagePercent` (0-100)504505## Conventions506507- **IDs are strings.** Bigint values; treat as opaque strings, never parse as numbers508- **Timestamps are ISO 8601 UTC.** Example: `2026-02-24T10:30:00.000Z`509- **Errors return JSON.** Format: `{ "error": "error_code" }`510- **Cursors are opaque.** Pass `nextCursor` as the `after` query parameter, never decode511- Export formats: `csv`, `xlsx`, `md` via `GET /extractions/{id}/export?format=csv` or `GET /draws/{id}/export?format=csv&type=winners`512513## Reference Files514515For additional detail beyond this guide:516517- **`references/mcp-tools.md`**: All 18 legacy v1 MCP tools with input/output schemas, annotations, selection rules, workflow patterns, common mistakes, and unsupported operations518- **`references/api-endpoints.md`**: All REST API endpoints with methods, paths, parameters, and response shapes519- **`references/python-examples.md`**: Python equivalents of all JavaScript examples (retry, extraction, draw, webhook)520- **`references/webhooks.md`**: Extended webhook examples, local testing with ngrok, delivery status monitoring521- **`references/mcp-setup.md`**: MCP server configuration for 10 IDEs and AI agent platforms522- **`references/extractions.md`**: Extraction tool details, export columns523- **`references/types.md`**: TypeScript type definitions for all REST API and MCP output objects