Xpoz TypeScript SDK
TypeScript SDK for the Xpoz social media intelligence platform. Query Twitter/X, Instagram, Reddit, and TikTok data through a simple, typed interface.
Installation
npm install @xpoz/xpoz
Requires Node.js 18+.
Get an API Key
Sign up and get your token at https://xpoz.ai/get-token.
Once you have it, pass it directly or set the XPOZ_API_KEY environment variable:
export XPOZ_API_KEY=your-token-here
What is Xpoz?
Xpoz provides unified access to social media data across Twitter/X, Instagram, Reddit, and TikTok. The platform indexes billions of posts, user profiles, and engagement metrics — making it possible to search, analyze, and export social media data at scale.
The SDK wraps Xpoz's MCP server, abstracting away transport, authentication, operation polling, and pagination into a clean developer-friendly API.
Features
- 42 data methods across Twitter, Instagram, Reddit, and TikTok
- Fully async — all methods return
Promise<T> - Automatic operation polling — long-running queries are abstracted away
- Response types — choose between fast (immediate), paging (full pagination), or CSV export
- Server-side pagination —
PaginatedResult<T>withnextPage(),getPage(n) - CSV export —
exportCsv()on any paginated result - Field selection — request only the fields you need
- TypeScript-first — fully typed results with autocomplete support
- Namespaced API —
client.twitter.*,client.instagram.*,client.reddit.*,client.tiktok.*,client.tracking.*
Quick Start
import { XpozClient, ResponseType } from "@xpoz/xpoz";
const client = new XpozClient({ apiKey: "your-api-key" });
await client.connect();
const user = await client.twitter.getUser("elonmusk");
console.log(`${user.name} — ${user.followersCount?.toLocaleString()} followers`);
const results = await client.twitter.searchPosts("artificial intelligence", {
startDate: "2025-01-01",
});
for (const post of results.data) {
console.log(post.text, post.likeCount);
}
await client.close();
Authentication
Get your API key at https://xpoz.ai/get-token, then use it as follows:
// Pass API key directly
const client = new XpozClient({ apiKey: "your-api-key" });
// Or use XPOZ_API_KEY environment variable
const client = new XpozClient();
// Custom server URL (also reads XPOZ_SERVER_URL env var)
const client = new XpozClient({ apiKey: "your-api-key", serverUrl: "https://xpoz.ai/mcp" });
// Custom operation timeout in milliseconds (default: 300000)
const client = new XpozClient({ apiKey: "your-api-key", timeoutMs: 600_000 });
Async Disposal
// Using Symbol.asyncDispose (Node.js 18.2+ with --experimental-vm-modules or TypeScript 5.2+)
await using client = new XpozClient({ apiKey: "your-api-key" });
await client.connect();
const user = await client.twitter.getUser("elonmusk");
// client.close() is called automatically
// Manual connect/close
const client = new XpozClient({ apiKey: "your-api-key" });
await client.connect();
try {
const results = await client.twitter.searchPosts("AI");
} finally {
await client.close();
}
Pagination
Methods that return large datasets use server-side pagination (100 items per page). These return a PaginatedResult<T> with built-in helpers:
const results = await client.twitter.searchPosts("AI");
results.data // TwitterPost[] — current page
results.pagination.totalRows // total matching rows
results.pagination.totalPages // total pages
results.pagination.pageNumber // current page number
results.pagination.pageSize // items per page (100)
results.pagination.resultsCount // items on current page
results.hasNextPage() // boolean
// Navigate pages
const page2 = await results.nextPage(); // fetch next page
const page5 = await results.getPage(5); // jump to specific page
// Export to CSV
const csvUrl = await results.exportCsv(); // returns download URL
Live Data — client.instagramLive and client.twitterLive
Live methods bypass the database and fetch straight from the crawler API, so results are always current. They page with an opaque cursor rather than page numbers, and return a CursorResult<T>:
const page = await client.instagramLive.searchPosts("travel", { fields: ["id", "caption"] });
page.data; // InstagramPost[] — this page
page.hasMore; // another page is available upstream
page.nextPageCursor; // opaque token for the next call
page.hasNextPage(); // boolean
const next = await page.nextPage();
// Walk every page
for await (const post of page.items()) {
console.log(post.id);
}
Cursor paging is forward-only: there is no getPage(n), totalPages, or totalRows, because the upstream API does not report them. Drive iteration off hasMore and the cursor — never off the item count, since a page can be short or empty while hasMore is still true.
These routes talk to the Xpoz REST API rather than the MCP server, so connect() is not required to use them. Override the base URL with new XpozClient({ apiUrl }) or the XPOZ_API_URL environment variable.
They always trigger a live fetch and require a paid account — free-tier accounts receive AuthenticationError (HTTP 403).
interactionType is "commenters" or "likers"; connectionType is "followers" or "following".
client.twitterLive
Same cursor paging, backed by the Twitter/X live routes:
const page = await client.twitterLive.searchPosts("open source", {
since: "2026-01-01",
sortBy: "latest",
fields: ["id", "text", "likeCount"],
});
for await (const tweet of page.items()) {
console.log(tweet.text);
}
searchPosts also accepts since, until (YYYY-MM-DD), lang, countryCode, and sortBy ("relevance" or "latest"); getPostsByUser accepts since and until. interactionType is "commenters", "quoters", or "retweeters"; connectionType is "followers" or "following". Retweets have no live route.
Field Selection
All methods accept a fields option. Use camelCase field names.
// Only fetch the fields you need (faster + less memory)
const results = await client.twitter.searchPosts("AI", {
fields: ["id", "text", "likeCount", "retweetCount", "createdAtDate"],
});
const user = await client.twitter.getUser("elonmusk", {
fields: ["id", "username", "name", "followersCount", "description"],
});
Requesting fewer fields significantly improves response time.
Response Types
Search and query methods support a responseType option that controls how results are returned. Import the ResponseType enum:
import { XpozClient, ResponseType } from "@xpoz/xpoz";
Fast mode (default)
The default behavior. Returns results immediately without polling. Use limit to constrain the number of results (max 300):
const results = await client.twitter.searchPosts("bitcoin", {
startDate: "2025-01-01",
responseType: ResponseType.Fast,
limit: 50,
});
console.log(results.data.length); // up to 50 results, returned immediately
Paging mode
Returns paginated results with full totalRows, totalPages, and tableName for cursor-based navigation:
const results = await client.twitter.searchPosts("bitcoin", {
startDate: "2025-01-01",
responseType: ResponseType.Paging, // optional — this is the default
});
console.log(results.pagination.totalRows); // total matching rows
if (results.hasNextPage()) {
const page2 = await results.nextPage();
}
CSV mode
Initiates an async export. Call exportCsv() on the result to poll the export operation and get a download URL:
const results = await client.twitter.searchPosts("bitcoin", {
startDate: "2025-01-01",
responseType: ResponseType.Csv,
});
const downloadUrl = await results.exportCsv();
console.log(downloadUrl); // URL to download the CSV file
Methods supporting responseType and limit
The following methods accept both responseType and limit:
twitter.getPostsByAuthor(),twitter.searchPosts(),twitter.getUsersByKeywords()instagram.getPostsByUser(),instagram.searchPosts(),instagram.getUsersByKeywords()reddit.searchPosts()tiktok.getPostsByUser(),tiktok.searchPosts(),tiktok.getUsersByKeywords(),tiktok.getPostsByHashtags(),tiktok.getUsersByHashtags(),tiktok.getPostsBySound()
These methods accept limit only:
twitter.searchUsers(),instagram.searchUsers(),reddit.searchUsers(),reddit.searchSubreddits()tiktok.searchUsers(),tiktok.searchSounds()
Query Syntax
The query parameter on all search* and get*ByKeywords methods supports a Lucene-style full-text syntax across Twitter, Instagram, and Reddit.
Exact phrase
Wrap in double quotes to require an exact match:
"machine learning"
"climate change"
Keywords (any word)
Space-separated terms without quotes match posts containing any of the words:
AI crypto blockchain
Boolean operators
Use AND, OR, NOT (case-insensitive). A bare space is treated as OR — be explicit:
"deep learning" AND python
tensorflow OR pytorch
climate NOT politics
Grouping with parentheses
(AI OR "artificial intelligence") AND ethics
(startup OR entrepreneur) NOT "venture capital"
Combined example
const results = await client.twitter.searchPosts(
'("machine learning" OR "deep learning") AND python NOT spam',
{
startDate: "2025-01-01",
language: "en",
}
);
Note: Do not use
from:,lang:,since:, oruntil:in the query string — use the dedicated parameters (authorUsername,language,startDate,endDate) instead.
Error Handling
import {
XpozError,
AuthenticationError,
XpozConnectionError,
OperationTimeoutError,
OperationFailedError,
OperationCancelledError,
ResponseType,
} from "@xpoz/xpoz";
try {
const user = await client.twitter.getUser("nonexistent_user_12345");
} catch (e) {
if (e instanceof OperationFailedError) {
console.log(`Operation ${e.operationId} failed: ${e.operationError}`);
} else if (e instanceof OperationTimeoutError) {
console.log(`Timed out after ${Math.round(e.elapsedMs / 1000)}s`);
} else if (e instanceof AuthenticationError) {
console.log("Invalid API key");
} else if (e instanceof XpozError) {
console.log(`Xpoz error: ${e.message}`);
}
}
API Reference
Twitter — client.twitter
getUser(identifier, options?) -> Promise<TwitterUser>
Get a single Twitter user profile.
// By username (default)
const user = await client.twitter.getUser("elonmusk");
// By numeric ID
const user = await client.twitter.getUser("44196397", { identifierType: "id" });
searchUsers(name, options?) -> Promise<TwitterUser[]>
Search users by name or username. Returns up to 10 results by default. Use limit to adjust.
const users = await client.twitter.searchUsers("elon");
const topFive = await client.twitter.searchUsers("elon", { limit: 5 });
getUserConnections(username, connectionType, options?) -> Promise<PaginatedResult<TwitterUser>>
Get followers or following for a user.
const followers = await client.twitter.getUserConnections("elonmusk", "followers");
const following = await client.twitter.getUserConnections("elonmusk", "following");
getUsersByKeywords(query, options?) -> Promise<PaginatedResult<TwitterUser>>
Find users who authored posts matching a keyword query. Supports responseType and limit.
const users = await client.twitter.getUsersByKeywords('"machine learning"', {
fields: ["username", "name", "followersCount"],
responseType: ResponseType.Fast,
limit: 20,
});
getPostsByIds(postIds, options?) -> Promise<TwitterPost[]>
Get 1-100 posts by their IDs.
const tweets = await client.twitter.getPostsByIds(["1234567890", "0987654321"]);
getPostsByAuthor(identifier, options?) -> Promise<PaginatedResult<TwitterPost>>
Get all posts by an author with optional date filtering. Supports responseType and limit.
const results = await client.twitter.getPostsByAuthor("elonmusk", {
startDate: "2025-01-01",
responseType: ResponseType.Fast,
limit: 100,
});
searchPosts(query, options?) -> Promise<PaginatedResult<TwitterPost>>
Full-text search with filters. Supports exact phrases ("machine learning"), boolean operators (AI AND python), and parentheses. Supports responseType and limit.
const results = await client.twitter.searchPosts('"artificial intelligence" AND ethics', {
startDate: "2025-01-01",
endDate: "2025-06-01",
language: "en",
fields: ["id", "text", "likeCount", "authorUsername", "createdAtDate"],
responseType: ResponseType.Fast,
limit: 50,
});
getRetweets(postId, options?) -> Promise<PaginatedResult<TwitterPost>>
Get retweets of a specific post (database only).
const retweets = await client.twitter.getRetweets("1234567890");
getQuotes(postId, options?) -> Promise<PaginatedResult<TwitterPost>>
Get quote tweets of a specific post.
const quotes = await client.twitter.getQuotes("1234567890");
getComments(postId, options?) -> Promise<PaginatedResult<TwitterPost>>
Get replies to a specific post.
const comments = await client.twitter.getComments("1234567890");
getPostInteractingUsers(postId, interactionType, options?) -> Promise<PaginatedResult<TwitterUser>>
Get users who interacted with a post. interactionType: "commenters", "quoters", "retweeters".
const commenters = await client.twitter.getPostInteractingUsers("1234567890", "commenters");
countPosts(phrase, options?) -> Promise<number>
Count tweets containing a phrase within a date range.
const count = await client.twitter.countPosts("bitcoin", { startDate: "2025-01-01" });
console.log(`${count.toLocaleString()} tweets mention bitcoin`);
Instagram — client.instagram
getUser(identifier, options?) -> Promise<InstagramUser>
const user = await client.instagram.getUser("instagram");
console.log(`${user.fullName} — ${user.followerCount?.toLocaleString()} followers`);
searchUsers(name, options?) -> Promise<InstagramUser[]>
Search users by name. Use limit to adjust the number of results.
const users = await client.instagram.searchUsers("nasa");
const topThree = await client.instagram.searchUsers("nasa", { limit: 3 });
getUserConnections(username, connectionType, options?) -> Promise<PaginatedResult<InstagramUser>>
const followers = await client.instagram.getUserConnections("instagram", "followers");
getUsersByKeywords(query, options?) -> Promise<PaginatedResult<InstagramUser>>
Find users who authored posts matching a keyword query. Supports responseType and limit.
const users = await client.instagram.getUsersByKeywords('"sustainable fashion"', {
responseType: ResponseType.Fast,
limit: 20,
});
getPostsByIds(postIds, options?) -> Promise<InstagramPost[]>
Post IDs must be in strong_id format: "media_id_user_id" (e.g. "3606450040306139062_4836333238").
const posts = await client.instagram.getPostsByIds(["3606450040306139062_4836333238"]);
getPostsByUser(identifier, options?) -> Promise<PaginatedResult<InstagramPost>>
Get all posts by a user. Supports responseType and limit.
const results = await client.instagram.getPostsByUser("nasa", {
responseType: ResponseType.Fast,
limit: 50,
});
searchPosts(query, options?) -> Promise<PaginatedResult<InstagramPost>>
Full-text search with filters. Supports responseType and limit.
const results = await client.instagram.searchPosts("travel photography", {
responseType: ResponseType.Fast,
limit: 30,
});
getComments(postId, options?) -> Promise<PaginatedResult<InstagramComment>>
const comments = await client.instagram.getComments("3606450040306139062_4836333238");
getPostInteractingUsers(postId, interactionType, options?) -> Promise<PaginatedResult<InstagramUser>>
interactionType: "commenters", "likers".
const likers = await client.instagram.getPostInteractingUsers(
"3606450040306139062_4836333238",
"likers"
);
Reddit — client.reddit
getUser(username, options?) -> Promise<RedditUser>
const user = await client.reddit.getUser("spez");
console.log(`${user.username} — ${user.totalKarma?.toLocaleString()} karma`);
searchUsers(name, options?) -> Promise<RedditUser[]>
Search users by name. Use limit to adjust the number of results.
const users = await client.reddit.searchUsers("spez");
const topThree = await client.reddit.searchUsers("spez", { limit: 3 });
getUsersByKeywords(query, options?) -> Promise<PaginatedResult<RedditUser>>
const users = await client.reddit.getUsersByKeywords('"machine learning"', {
subreddit: "MachineLearning",
});
searchPosts(query, options?) -> Promise<PaginatedResult<RedditPost>>
sort: "relevance", "hot", "top", "new", "comments". time: "hour", "day", "week", "month", "year", "all". Supports responseType and limit.
const results = await client.reddit.searchPosts("python tutorial", {
subreddit: "learnpython",
sort: "top",
time: "month",
responseType: ResponseType.Fast,
limit: 25,
});
getPostWithComments(postId, options?) -> Promise<RedditPostWithComments>
Returns an object with the post and its comments.
const result = await client.reddit.getPostWithComments("abc123");
console.log(result.post.title);
for (const comment of result.comments) {
console.log(` ${comment.authorUsername}: ${comment.body?.slice(0, 80)}`);
}
searchComments(query, options?) -> Promise<PaginatedResult<RedditComment>>
const comments = await client.reddit.searchComments("helpful tip", {
subreddit: "LifeProTips",
});
getCommentById(commentId, options?) -> Promise<RedditComment>
Fetch a single Reddit comment by its id (bare base36 or t1_-prefixed). Database-first with live API fallback when the comment is missing or stale.
const comment = await client.reddit.getCommentById("laz1ytq", {
fields: ["id", "body", "rank", "removal"],
});
searchSubreddits(query, options?) -> Promise<RedditSubreddit[]>
Search subreddits by name. Use limit to adjust the number of results.
const subs = await client.reddit.searchSubreddits("machine learning");
const topFive = await client.reddit.searchSubreddits("machine learning", { limit: 5 });
getSubredditWithPosts(subredditName, options?) -> Promise<SubredditWithPosts>
const result = await client.reddit.getSubredditWithPosts("wallstreetbets");
console.log(`r/${result.subreddit.displayName} — ${result.subreddit.subscribersCount?.toLocaleString()} members`);
for (const post of result.posts) {
console.log(` ${post.title} (${post.score} points)`);
}
getSubredditsByKeywords(query, options?) -> Promise<PaginatedResult<RedditSubreddit>>
const subs = await client.reddit.getSubredditsByKeywords("cryptocurrency");
TikTok — client.tiktok
getUser(identifier, options?) -> Promise<TiktokUser>
const user = await client.tiktok.getUser("charlidamelio");
console.log(`${user.nickname} — ${user.followerCount?.toLocaleString()} followers`);
// By numeric ID
const user = await client.tiktok.getUser("123456789", { identifierType: "id" });
searchUsers(name, options?) -> Promise<TiktokUser[]>
Search users by name. Use limit to adjust the number of results.
const users = await client.tiktok.searchUsers("charli");
const topFive = await client.tiktok.searchUsers("charli", { limit: 5 });
getUsersByKeywords(query, options?) -> Promise<PaginatedResult<TiktokUser>>
Find users who authored posts matching a keyword query. Supports responseType and limit.
const users = await client.tiktok.getUsersByKeywords('"machine learning"', {
responseType: ResponseType.Fast,
limit: 20,
});
getPostsByIds(postIds, options?) -> Promise<TiktokPost[]>
Get 1-100 posts by their IDs.
const posts = await client.tiktok.getPostsByIds(["7123456789012345678"]);
getPostsByUser(identifier, options?) -> Promise<PaginatedResult<TiktokPost>>
Get all posts by a user. Supports responseType and limit.
const results = await client.tiktok.getPostsByUser("charlidamelio", {
startDate: "2025-01-01",
responseType: ResponseType.Fast,
limit: 50,
});
searchPosts(query, options?) -> Promise<PaginatedResult<TiktokPost>>
Full-text search with filters. Supports responseType and limit.
const results = await client.tiktok.searchPosts("travel vlog", {
startDate: "2025-01-01",
responseType: ResponseType.Fast,
limit: 30,
});
getPostsByHashtags(hashtags, options?) -> Promise<PaginatedResult<TiktokPost>>
Search posts by hashtags via the indexed hashtags column. Pass bare alphanumeric tags (no leading #). Max 5 hashtags per request; OR semantics across the list.
const results = await client.tiktok.getPostsByHashtags(["dance", "fyp"], {
responseType: ResponseType.Fast,
limit: 50,
});
getUsersByHashtags(hashtags, options?) -> Promise<PaginatedResult<TiktokUser>>
Find users who authored posts tagged with the given hashtags. Same input rules as getPostsByHashtags.
const users = await client.tiktok.getUsersByHashtags(["sustainable_fashion"], {
responseType: ResponseType.Fast,
limit: 20,
});
searchSounds(keyword, options?) -> Promise<TiktokSound[]>
Search sound/music objects by keyword (title or artist). Pass the returned id to getPostsBySound.
const sounds = await client.tiktok.searchSounds("dance monkey");
const soundId = sounds[0].id;
getPostsBySound(soundId, options?) -> Promise<PaginatedResult<TiktokPost>>
Find posts that use a specific sound via the indexed music_id column. Pass a single numeric soundId from searchSounds.
const results = await client.tiktok.getPostsBySound("7016548364456789012", {
responseType: ResponseType.Fast,
limit: 50,
});
getComments(postId, options?) -> Promise<PaginatedResult<TiktokComment>>
const comments = await client.tiktok.getComments("7123456789012345678");
Tracking — client.tracking
Manage tracked items (keywords, users, subreddits) that Xpoz monitors on your behalf. Import the enums to build items:
import { XpozClient, TrackedItemType, TrackedItemPlatform } from "@xpoz/xpoz";
getTrackedItems() -> Promise<TrackedItem[]>
List all currently tracked items on your account.
const items = await client.tracking.getTrackedItems();
for (const item of items) {
console.log(`${item.platform} / ${item.type}: ${item.phrase}`);
}
addTrackedItems(items) -> Promise<AddTrackedItemsResult>
Add one or more items to track.
const result = await client.tracking.addTrackedItems([
{ phrase: "bitcoin", type: TrackedItemType.Keyword, platform: TrackedItemPlatform.Twitter },
{ phrase: "nasa", type: TrackedItemType.User, platform: TrackedItemPlatform.Instagram },
]);
console.log(`Added ${result.addedCount} items (${result.currentCount}/${result.maxTrackedItems} used)`);
removeTrackedItems(items) -> Promise<RemoveTrackedItemsResult>
Remove one or more tracked items.
const result = await client.tracking.removeTrackedItems([
{ phrase: "bitcoin", type: TrackedItemType.Keyword, platform: TrackedItemPlatform.Twitter },
]);
console.log(`Removed ${result.removedCount} items`);
Account — client.account
Read-only account, plan, and usage information for the authenticated user.
getAccountDetails() -> Promise<AccountDetails>
Returns the plan (name + feature limits), billing (period + next renewal; billing is null on the Free plan), and current usage (remaining subscription/extra credits, extra tracked items).
const details = await client.account.getAccountDetails();
console.log(details.plan.name, details.usage.subscriptionCreditsRemaining);
getCreditsUsageHistory(range?, granularity?) -> Promise<CreditsUsageHistory>
Returns time-series usage for credits and export rows. range is one of "today", "7d", "current_month" (default), "lifetime"; granularity is "hour" or "day" (default). For current remaining balances, use getAccountDetails().
const history = await client.account.getCreditsUsageHistory("7d", "day");
for (const bucket of history.credits) {
console.log(bucket.bucket, bucket.totalUsed);
}
Type Models
All fields are optional and typed as their respective TypeScript types. Unknown fields are preserved on the object.
Date fields. Post models expose up to three creation-time fields whose runtime formats differ from what their names suggest:
createdAt— full ISO 8601 datetime on Twitter; epoch seconds on Reddit/Instagram/TikTok.createdAtTimestamp— full ISO 8601 datetime (Reddit/Instagram/TikTok; not returned for Twitter).createdAtDate— the creation date only, rendered as ISO midnight (2026-07-20T00:00:00.000Z); it carries no real time-of-day.Depending on response quoting, values can arrive as strings or numbers — treat all three as
string | number.
TwitterPost
TwitterUser
InstagramPost
InstagramUser
InstagramComment
RedditPost
RedditUser
RedditComment
RedditSubreddit
TiktokPost
TiktokUser
TiktokComment
TiktokSound
AccountDetails
Returned by getAccountDetails().
PlanFeatures
AccountBilling
AccountUsage
CreditsUsageHistory
Returned by getCreditsUsageHistory().
UsageHistoryBucket
TrackedItem
AddTrackedItemsResult
RemoveTrackedItemsResult
Composite Types
RedditPostWithComments — returned by getPostWithComments():
post: RedditPostcomments: RedditComment[]commentsPagination: PaginationInfo | nullcommentsTableName: string | null
SubredditWithPosts — returned by getSubredditWithPosts():
subreddit: RedditSubredditposts: RedditPost[]postsPagination: PaginationInfo | nullpostsTableName: string | null
Environment Variables
Testing
Tests hit the live Xpoz API and require a valid API key:
XPOZ_API_KEY=your-api-key npx vitest run
License
MIT