@xpoz/xpoz 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +663 -0
- package/dist/index.cjs +949 -0
- package/dist/index.d.cts +579 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +914 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
# Xpoz TypeScript SDK
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the [Xpoz](https://xpoz.ai) social media intelligence platform. Query Twitter/X, Instagram, and Reddit data through a simple, typed interface.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install xpoz
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+.
|
|
12
|
+
|
|
13
|
+
## Get an API Key
|
|
14
|
+
|
|
15
|
+
Sign up and get your token at **https://xpoz.ai/get-token**.
|
|
16
|
+
|
|
17
|
+
Once you have it, pass it directly or set the `XPOZ_API_KEY` environment variable:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export XPOZ_API_KEY=your-token-here
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What is Xpoz?
|
|
24
|
+
|
|
25
|
+
Xpoz provides unified access to social media data across Twitter/X, Instagram, and Reddit. The platform indexes billions of posts, user profiles, and engagement metrics — making it possible to search, analyze, and export social media data at scale.
|
|
26
|
+
|
|
27
|
+
The SDK wraps Xpoz's [MCP](https://modelcontextprotocol.io) server, abstracting away transport, authentication, operation polling, and pagination into a clean developer-friendly API.
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **30 data methods** across Twitter, Instagram, and Reddit
|
|
32
|
+
- **Fully async** — all methods return `Promise<T>`
|
|
33
|
+
- **Automatic operation polling** — long-running queries are abstracted away
|
|
34
|
+
- **Server-side pagination** — `PaginatedResult<T>` with `nextPage()`, `getPage(n)`
|
|
35
|
+
- **CSV export** — `exportCsv()` on any paginated result
|
|
36
|
+
- **Field selection** — request only the fields you need
|
|
37
|
+
- **TypeScript-first** — fully typed results with autocomplete support
|
|
38
|
+
- **Namespaced API** — `client.twitter.*`, `client.instagram.*`, `client.reddit.*`
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { XpozClient } from "xpoz";
|
|
44
|
+
|
|
45
|
+
const client = new XpozClient({ apiKey: "your-api-key" });
|
|
46
|
+
await client.connect();
|
|
47
|
+
|
|
48
|
+
const user = await client.twitter.getUser("elonmusk");
|
|
49
|
+
console.log(`${user.name} — ${user.followersCount?.toLocaleString()} followers`);
|
|
50
|
+
|
|
51
|
+
const results = await client.twitter.searchPosts("artificial intelligence", {
|
|
52
|
+
startDate: "2025-01-01",
|
|
53
|
+
});
|
|
54
|
+
for (const post of results.data) {
|
|
55
|
+
console.log(post.text, post.likeCount);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await client.close();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Authentication
|
|
62
|
+
|
|
63
|
+
Get your API key at https://xpoz.ai/get-token, then use it as follows:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Pass API key directly
|
|
67
|
+
const client = new XpozClient({ apiKey: "your-api-key" });
|
|
68
|
+
|
|
69
|
+
// Or use XPOZ_API_KEY environment variable
|
|
70
|
+
const client = new XpozClient();
|
|
71
|
+
|
|
72
|
+
// Custom server URL (also reads XPOZ_SERVER_URL env var)
|
|
73
|
+
const client = new XpozClient({ apiKey: "your-api-key", serverUrl: "https://xpoz.ai/mcp" });
|
|
74
|
+
|
|
75
|
+
// Custom operation timeout in milliseconds (default: 300000)
|
|
76
|
+
const client = new XpozClient({ apiKey: "your-api-key", timeoutMs: 600_000 });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Async Disposal
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Using Symbol.asyncDispose (Node.js 18.2+ with --experimental-vm-modules or TypeScript 5.2+)
|
|
83
|
+
await using client = new XpozClient({ apiKey: "your-api-key" });
|
|
84
|
+
await client.connect();
|
|
85
|
+
const user = await client.twitter.getUser("elonmusk");
|
|
86
|
+
// client.close() is called automatically
|
|
87
|
+
|
|
88
|
+
// Manual connect/close
|
|
89
|
+
const client = new XpozClient({ apiKey: "your-api-key" });
|
|
90
|
+
await client.connect();
|
|
91
|
+
try {
|
|
92
|
+
const results = await client.twitter.searchPosts("AI");
|
|
93
|
+
} finally {
|
|
94
|
+
await client.close();
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Pagination
|
|
99
|
+
|
|
100
|
+
Methods that return large datasets use server-side pagination (100 items per page). These return a `PaginatedResult<T>` with built-in helpers:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const results = await client.twitter.searchPosts("AI");
|
|
104
|
+
|
|
105
|
+
results.data // TwitterPost[] — current page
|
|
106
|
+
results.pagination.totalRows // total matching rows
|
|
107
|
+
results.pagination.totalPages // total pages
|
|
108
|
+
results.pagination.pageNumber // current page number
|
|
109
|
+
results.pagination.pageSize // items per page (100)
|
|
110
|
+
results.pagination.resultsCount // items on current page
|
|
111
|
+
results.hasNextPage() // boolean
|
|
112
|
+
|
|
113
|
+
// Navigate pages
|
|
114
|
+
const page2 = await results.nextPage(); // fetch next page
|
|
115
|
+
const page5 = await results.getPage(5); // jump to specific page
|
|
116
|
+
|
|
117
|
+
// Export to CSV
|
|
118
|
+
const csvUrl = await results.exportCsv(); // returns download URL
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Field Selection
|
|
122
|
+
|
|
123
|
+
All methods accept a `fields` option. Use camelCase field names.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// Only fetch the fields you need (faster + less memory)
|
|
127
|
+
const results = await client.twitter.searchPosts("AI", {
|
|
128
|
+
fields: ["id", "text", "likeCount", "retweetCount", "createdAtDate"],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const user = await client.twitter.getUser("elonmusk", {
|
|
132
|
+
fields: ["id", "username", "name", "followersCount", "description"],
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Requesting fewer fields significantly improves response time.
|
|
137
|
+
|
|
138
|
+
## Query Syntax
|
|
139
|
+
|
|
140
|
+
The `query` parameter on all `search*` and `get*ByKeywords` methods supports a Lucene-style full-text syntax across Twitter, Instagram, and Reddit.
|
|
141
|
+
|
|
142
|
+
### Exact phrase
|
|
143
|
+
Wrap in double quotes to require an exact match:
|
|
144
|
+
```
|
|
145
|
+
"machine learning"
|
|
146
|
+
"climate change"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Keywords (any word)
|
|
150
|
+
Space-separated terms without quotes match posts containing **any** of the words:
|
|
151
|
+
```
|
|
152
|
+
AI crypto blockchain
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Boolean operators
|
|
156
|
+
Use `AND`, `OR`, `NOT` (case-insensitive). A bare space is treated as `OR` — be explicit:
|
|
157
|
+
```
|
|
158
|
+
"deep learning" AND python
|
|
159
|
+
tensorflow OR pytorch
|
|
160
|
+
climate NOT politics
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Grouping with parentheses
|
|
164
|
+
```
|
|
165
|
+
(AI OR "artificial intelligence") AND ethics
|
|
166
|
+
(startup OR entrepreneur) NOT "venture capital"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Combined example
|
|
170
|
+
```typescript
|
|
171
|
+
const results = await client.twitter.searchPosts(
|
|
172
|
+
'("machine learning" OR "deep learning") AND python NOT spam',
|
|
173
|
+
{
|
|
174
|
+
startDate: "2025-01-01",
|
|
175
|
+
language: "en",
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
> **Note:** Do not use `from:`, `lang:`, `since:`, or `until:` in the query string — use the dedicated parameters (`authorUsername`, `language`, `startDate`, `endDate`) instead.
|
|
181
|
+
|
|
182
|
+
## Error Handling
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import {
|
|
186
|
+
XpozError,
|
|
187
|
+
AuthenticationError,
|
|
188
|
+
XpozConnectionError,
|
|
189
|
+
OperationTimeoutError,
|
|
190
|
+
OperationFailedError,
|
|
191
|
+
OperationCancelledError,
|
|
192
|
+
} from "xpoz";
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const user = await client.twitter.getUser("nonexistent_user_12345");
|
|
196
|
+
} catch (e) {
|
|
197
|
+
if (e instanceof OperationFailedError) {
|
|
198
|
+
console.log(`Operation ${e.operationId} failed: ${e.operationError}`);
|
|
199
|
+
} else if (e instanceof OperationTimeoutError) {
|
|
200
|
+
console.log(`Timed out after ${Math.round(e.elapsedMs / 1000)}s`);
|
|
201
|
+
} else if (e instanceof AuthenticationError) {
|
|
202
|
+
console.log("Invalid API key");
|
|
203
|
+
} else if (e instanceof XpozError) {
|
|
204
|
+
console.log(`Xpoz error: ${e.message}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## API Reference
|
|
212
|
+
|
|
213
|
+
### Twitter — `client.twitter`
|
|
214
|
+
|
|
215
|
+
#### `getUser(identifier, options?) -> Promise<TwitterUser>`
|
|
216
|
+
|
|
217
|
+
Get a single Twitter user profile.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// By username (default)
|
|
221
|
+
const user = await client.twitter.getUser("elonmusk");
|
|
222
|
+
|
|
223
|
+
// By numeric ID
|
|
224
|
+
const user = await client.twitter.getUser("44196397", { identifierType: "id" });
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### `searchUsers(name, options?) -> Promise<TwitterUser[]>`
|
|
228
|
+
|
|
229
|
+
Search users by name or username. Returns up to 10 results.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const users = await client.twitter.searchUsers("elon");
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### `getUserConnections(username, connectionType, options?) -> Promise<PaginatedResult<TwitterUser>>`
|
|
236
|
+
|
|
237
|
+
Get followers or following for a user.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const followers = await client.twitter.getUserConnections("elonmusk", "followers");
|
|
241
|
+
const following = await client.twitter.getUserConnections("elonmusk", "following");
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### `getUsersByKeywords(query, options?) -> Promise<PaginatedResult<TwitterUser>>`
|
|
245
|
+
|
|
246
|
+
Find users who authored posts matching a keyword query.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
const users = await client.twitter.getUsersByKeywords('"machine learning"', {
|
|
250
|
+
fields: ["username", "name", "followersCount"],
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### `getPostsByIds(postIds, options?) -> Promise<TwitterPost[]>`
|
|
255
|
+
|
|
256
|
+
Get 1-100 posts by their IDs.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const tweets = await client.twitter.getPostsByIds(["1234567890", "0987654321"]);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### `getPostsByAuthor(identifier, options?) -> Promise<PaginatedResult<TwitterPost>>`
|
|
263
|
+
|
|
264
|
+
Get all posts by an author with optional date filtering.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const results = await client.twitter.getPostsByAuthor("elonmusk", {
|
|
268
|
+
startDate: "2025-01-01",
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### `searchPosts(query, options?) -> Promise<PaginatedResult<TwitterPost>>`
|
|
273
|
+
|
|
274
|
+
Full-text search with filters. Supports exact phrases (`"machine learning"`), boolean operators (`AI AND python`), and parentheses.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const results = await client.twitter.searchPosts('"artificial intelligence" AND ethics', {
|
|
278
|
+
startDate: "2025-01-01",
|
|
279
|
+
endDate: "2025-06-01",
|
|
280
|
+
language: "en",
|
|
281
|
+
fields: ["id", "text", "likeCount", "authorUsername", "createdAtDate"],
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### `getRetweets(postId, options?) -> Promise<PaginatedResult<TwitterPost>>`
|
|
286
|
+
|
|
287
|
+
Get retweets of a specific post (database only).
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
const retweets = await client.twitter.getRetweets("1234567890");
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### `getQuotes(postId, options?) -> Promise<PaginatedResult<TwitterPost>>`
|
|
294
|
+
|
|
295
|
+
Get quote tweets of a specific post.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
const quotes = await client.twitter.getQuotes("1234567890");
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
#### `getComments(postId, options?) -> Promise<PaginatedResult<TwitterPost>>`
|
|
302
|
+
|
|
303
|
+
Get replies to a specific post.
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
const comments = await client.twitter.getComments("1234567890");
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### `getPostInteractingUsers(postId, interactionType, options?) -> Promise<PaginatedResult<TwitterUser>>`
|
|
310
|
+
|
|
311
|
+
Get users who interacted with a post. `interactionType`: `"commenters"`, `"quoters"`, `"retweeters"`.
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const commenters = await client.twitter.getPostInteractingUsers("1234567890", "commenters");
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### `countPosts(phrase, options?) -> Promise<number>`
|
|
318
|
+
|
|
319
|
+
Count tweets containing a phrase within a date range.
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
const count = await client.twitter.countPosts("bitcoin", { startDate: "2025-01-01" });
|
|
323
|
+
console.log(`${count.toLocaleString()} tweets mention bitcoin`);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
### Instagram — `client.instagram`
|
|
329
|
+
|
|
330
|
+
#### `getUser(identifier, options?) -> Promise<InstagramUser>`
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const user = await client.instagram.getUser("instagram");
|
|
334
|
+
console.log(`${user.fullName} — ${user.followerCount?.toLocaleString()} followers`);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
#### `searchUsers(name, options?) -> Promise<InstagramUser[]>`
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const users = await client.instagram.searchUsers("nasa");
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### `getUserConnections(username, connectionType, options?) -> Promise<PaginatedResult<InstagramUser>>`
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
const followers = await client.instagram.getUserConnections("instagram", "followers");
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### `getUsersByKeywords(query, options?) -> Promise<PaginatedResult<InstagramUser>>`
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const users = await client.instagram.getUsersByKeywords('"sustainable fashion"');
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### `getPostsByIds(postIds, options?) -> Promise<InstagramPost[]>`
|
|
356
|
+
|
|
357
|
+
Post IDs must be in strong_id format: `"media_id_user_id"` (e.g. `"3606450040306139062_4836333238"`).
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
const posts = await client.instagram.getPostsByIds(["3606450040306139062_4836333238"]);
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### `getPostsByUser(identifier, options?) -> Promise<PaginatedResult<InstagramPost>>`
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
const results = await client.instagram.getPostsByUser("nasa");
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### `searchPosts(query, options?) -> Promise<PaginatedResult<InstagramPost>>`
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
const results = await client.instagram.searchPosts("travel photography");
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
#### `getComments(postId, options?) -> Promise<PaginatedResult<InstagramComment>>`
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
const comments = await client.instagram.getComments("3606450040306139062_4836333238");
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### `getPostInteractingUsers(postId, interactionType, options?) -> Promise<PaginatedResult<InstagramUser>>`
|
|
382
|
+
|
|
383
|
+
`interactionType`: `"commenters"`, `"likers"`.
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
const likers = await client.instagram.getPostInteractingUsers(
|
|
387
|
+
"3606450040306139062_4836333238",
|
|
388
|
+
"likers"
|
|
389
|
+
);
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
### Reddit — `client.reddit`
|
|
395
|
+
|
|
396
|
+
#### `getUser(username, options?) -> Promise<RedditUser>`
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
const user = await client.reddit.getUser("spez");
|
|
400
|
+
console.log(`${user.username} — ${user.totalKarma?.toLocaleString()} karma`);
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
#### `searchUsers(name, options?) -> Promise<RedditUser[]>`
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
const users = await client.reddit.searchUsers("spez");
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### `getUsersByKeywords(query, options?) -> Promise<PaginatedResult<RedditUser>>`
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
const users = await client.reddit.getUsersByKeywords('"machine learning"', {
|
|
413
|
+
subreddit: "MachineLearning",
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### `searchPosts(query, options?) -> Promise<PaginatedResult<RedditPost>>`
|
|
418
|
+
|
|
419
|
+
`sort`: `"relevance"`, `"hot"`, `"top"`, `"new"`, `"comments"`. `time`: `"hour"`, `"day"`, `"week"`, `"month"`, `"year"`, `"all"`.
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
const results = await client.reddit.searchPosts("python tutorial", {
|
|
423
|
+
subreddit: "learnpython",
|
|
424
|
+
sort: "top",
|
|
425
|
+
time: "month",
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
#### `getPostWithComments(postId, options?) -> Promise<RedditPostWithComments>`
|
|
430
|
+
|
|
431
|
+
Returns an object with the post and its comments.
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
const result = await client.reddit.getPostWithComments("abc123");
|
|
435
|
+
console.log(result.post.title);
|
|
436
|
+
for (const comment of result.comments) {
|
|
437
|
+
console.log(` ${comment.authorUsername}: ${comment.body?.slice(0, 80)}`);
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### `searchComments(query, options?) -> Promise<PaginatedResult<RedditComment>>`
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
const comments = await client.reddit.searchComments("helpful tip", {
|
|
445
|
+
subreddit: "LifeProTips",
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
#### `searchSubreddits(query, options?) -> Promise<RedditSubreddit[]>`
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
const subs = await client.reddit.searchSubreddits("machine learning");
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### `getSubredditWithPosts(subredditName, options?) -> Promise<SubredditWithPosts>`
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
const result = await client.reddit.getSubredditWithPosts("wallstreetbets");
|
|
459
|
+
console.log(`r/${result.subreddit.displayName} — ${result.subreddit.subscribersCount?.toLocaleString()} members`);
|
|
460
|
+
for (const post of result.posts) {
|
|
461
|
+
console.log(` ${post.title} (${post.score} points)`);
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### `getSubredditsByKeywords(query, options?) -> Promise<PaginatedResult<RedditSubreddit>>`
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
const subs = await client.reddit.getSubredditsByKeywords("cryptocurrency");
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Type Models
|
|
474
|
+
|
|
475
|
+
All fields are optional and typed as their respective TypeScript types. Unknown fields are preserved on the object.
|
|
476
|
+
|
|
477
|
+
### TwitterPost
|
|
478
|
+
|
|
479
|
+
| Field | Type | Description |
|
|
480
|
+
| ------------------- | ---------- | -------------------------- |
|
|
481
|
+
| `id` | `string` | Post ID |
|
|
482
|
+
| `text` | `string` | Post text content |
|
|
483
|
+
| `authorId` | `string` | Author's user ID |
|
|
484
|
+
| `authorUsername` | `string` | Author's username |
|
|
485
|
+
| `likeCount` | `number` | Number of likes |
|
|
486
|
+
| `retweetCount` | `number` | Number of retweets |
|
|
487
|
+
| `replyCount` | `number` | Number of replies |
|
|
488
|
+
| `quoteCount` | `number` | Number of quotes |
|
|
489
|
+
| `impressionCount` | `number` | Number of impressions |
|
|
490
|
+
| `bookmarkCount` | `number` | Number of bookmarks |
|
|
491
|
+
| `lang` | `string` | Language code |
|
|
492
|
+
| `hashtags` | `string[]` | Hashtags in tweet |
|
|
493
|
+
| `mentions` | `string[]` | Mentioned usernames |
|
|
494
|
+
| `mediaUrls` | `string[]` | Media attachment URLs |
|
|
495
|
+
| `urls` | `string[]` | URLs in tweet |
|
|
496
|
+
| `country` | `string` | Country (if geo-tagged) |
|
|
497
|
+
| `createdAt` | `string` | Creation timestamp |
|
|
498
|
+
| `createdAtDate` | `string` | Creation date (YYYY-MM-DD) |
|
|
499
|
+
| `conversationId` | `string` | Thread conversation ID |
|
|
500
|
+
| `quotedTweetId` | `string` | ID of quoted tweet |
|
|
501
|
+
| `replyToTweetId` | `string` | ID of parent tweet |
|
|
502
|
+
| `isRetweet` | `boolean` | Whether this is a retweet |
|
|
503
|
+
| `possiblySensitive` | `boolean` | Sensitive content flag |
|
|
504
|
+
|
|
505
|
+
### TwitterUser
|
|
506
|
+
|
|
507
|
+
| Field | Type | Description |
|
|
508
|
+
| ---------------------------- | --------- | -------------------------- |
|
|
509
|
+
| `id` | `string` | User ID |
|
|
510
|
+
| `username` | `string` | Username (handle) |
|
|
511
|
+
| `name` | `string` | Display name |
|
|
512
|
+
| `description` | `string` | Bio text |
|
|
513
|
+
| `location` | `string` | Location string |
|
|
514
|
+
| `verified` | `boolean` | Verification status |
|
|
515
|
+
| `verifiedType` | `string` | Verification type |
|
|
516
|
+
| `followersCount` | `number` | Number of followers |
|
|
517
|
+
| `followingCount` | `number` | Number of following |
|
|
518
|
+
| `tweetCount` | `number` | Total tweets |
|
|
519
|
+
| `likesCount` | `number` | Total likes |
|
|
520
|
+
| `profileImageUrl` | `string` | Profile picture URL |
|
|
521
|
+
| `createdAt` | `string` | Account creation timestamp |
|
|
522
|
+
| `accountBasedIn` | `string` | Account location |
|
|
523
|
+
| `isInauthentic` | `boolean` | Inauthenticity flag |
|
|
524
|
+
| `isInauthenticProbScore` | `number` | Inauthenticity probability |
|
|
525
|
+
| `avgTweetsPerDayLastMonth` | `number` | Tweeting frequency |
|
|
526
|
+
|
|
527
|
+
### InstagramPost
|
|
528
|
+
|
|
529
|
+
| Field | Type | Description |
|
|
530
|
+
| ---------------- | -------- | -------------------------- |
|
|
531
|
+
| `id` | `string` | Post ID (strong_id format) |
|
|
532
|
+
| `caption` | `string` | Post caption |
|
|
533
|
+
| `username` | `string` | Author username |
|
|
534
|
+
| `fullName` | `string` | Author display name |
|
|
535
|
+
| `likeCount` | `number` | Number of likes |
|
|
536
|
+
| `commentCount` | `number` | Number of comments |
|
|
537
|
+
| `reshareCount` | `number` | Number of reshares |
|
|
538
|
+
| `videoPlayCount` | `number` | Video play count |
|
|
539
|
+
| `mediaType` | `string` | Media type |
|
|
540
|
+
| `imageUrl` | `string` | Image URL |
|
|
541
|
+
| `videoUrl` | `string` | Video URL |
|
|
542
|
+
| `createdAtDate` | `string` | Creation date |
|
|
543
|
+
|
|
544
|
+
### InstagramUser
|
|
545
|
+
|
|
546
|
+
| Field | Type | Description |
|
|
547
|
+
| ---------------- | --------- | ------------------- |
|
|
548
|
+
| `id` | `string` | User ID |
|
|
549
|
+
| `username` | `string` | Username |
|
|
550
|
+
| `fullName` | `string` | Display name |
|
|
551
|
+
| `biography` | `string` | Bio text |
|
|
552
|
+
| `isPrivate` | `boolean` | Private account |
|
|
553
|
+
| `isVerified` | `boolean` | Verified status |
|
|
554
|
+
| `followerCount` | `number` | Followers |
|
|
555
|
+
| `followingCount` | `number` | Following |
|
|
556
|
+
| `mediaCount` | `number` | Total posts |
|
|
557
|
+
| `profilePicUrl` | `string` | Profile picture URL |
|
|
558
|
+
|
|
559
|
+
### InstagramComment
|
|
560
|
+
|
|
561
|
+
| Field | Type | Description |
|
|
562
|
+
| ------------------- | -------- | --------------- |
|
|
563
|
+
| `id` | `string` | Comment ID |
|
|
564
|
+
| `text` | `string` | Comment text |
|
|
565
|
+
| `username` | `string` | Author username |
|
|
566
|
+
| `parentPostId` | `string` | Parent post ID |
|
|
567
|
+
| `likeCount` | `number` | Number of likes |
|
|
568
|
+
| `childCommentCount` | `number` | Reply count |
|
|
569
|
+
| `createdAtDate` | `string` | Creation date |
|
|
570
|
+
|
|
571
|
+
### RedditPost
|
|
572
|
+
|
|
573
|
+
| Field | Type | Description |
|
|
574
|
+
| ---------------- | --------- | --------------------- |
|
|
575
|
+
| `id` | `string` | Post ID |
|
|
576
|
+
| `title` | `string` | Post title |
|
|
577
|
+
| `selftext` | `string` | Post body text |
|
|
578
|
+
| `authorUsername` | `string` | Author username |
|
|
579
|
+
| `subredditName` | `string` | Subreddit name |
|
|
580
|
+
| `score` | `number` | Net score |
|
|
581
|
+
| `upvotes` | `number` | Upvote count |
|
|
582
|
+
| `commentsCount` | `number` | Comment count |
|
|
583
|
+
| `url` | `string` | Post URL |
|
|
584
|
+
| `permalink` | `string` | Reddit permalink |
|
|
585
|
+
| `isSelf` | `boolean` | Self post (text only) |
|
|
586
|
+
| `over18` | `boolean` | NSFW flag |
|
|
587
|
+
| `createdAtDate` | `string` | Creation date |
|
|
588
|
+
|
|
589
|
+
### RedditUser
|
|
590
|
+
|
|
591
|
+
| Field | Type | Description |
|
|
592
|
+
| -------------------- | --------- | --------------------- |
|
|
593
|
+
| `id` | `string` | User ID |
|
|
594
|
+
| `username` | `string` | Username |
|
|
595
|
+
| `totalKarma` | `number` | Total karma |
|
|
596
|
+
| `linkKarma` | `number` | Link karma |
|
|
597
|
+
| `commentKarma` | `number` | Comment karma |
|
|
598
|
+
| `isGold` | `boolean` | Reddit Gold status |
|
|
599
|
+
| `isMod` | `boolean` | Moderator status |
|
|
600
|
+
| `profileDescription` | `string` | Profile bio |
|
|
601
|
+
| `createdAtDate` | `string` | Account creation date |
|
|
602
|
+
|
|
603
|
+
### RedditComment
|
|
604
|
+
|
|
605
|
+
| Field | Type | Description |
|
|
606
|
+
| ---------------- | --------- | --------------- |
|
|
607
|
+
| `id` | `string` | Comment ID |
|
|
608
|
+
| `body` | `string` | Comment text |
|
|
609
|
+
| `authorUsername` | `string` | Author username |
|
|
610
|
+
| `parentPostId` | `string` | Parent post ID |
|
|
611
|
+
| `score` | `number` | Net score |
|
|
612
|
+
| `depth` | `number` | Nesting depth |
|
|
613
|
+
| `isSubmitter` | `boolean` | Is OP |
|
|
614
|
+
| `createdAtDate` | `string` | Creation date |
|
|
615
|
+
|
|
616
|
+
### RedditSubreddit
|
|
617
|
+
|
|
618
|
+
| Field | Type | Description |
|
|
619
|
+
| ------------------- | --------- | ----------------- |
|
|
620
|
+
| `id` | `string` | Subreddit ID |
|
|
621
|
+
| `displayName` | `string` | Subreddit name |
|
|
622
|
+
| `title` | `string` | Subreddit title |
|
|
623
|
+
| `publicDescription` | `string` | Short description |
|
|
624
|
+
| `description` | `string` | Full description |
|
|
625
|
+
| `subscribersCount` | `number` | Subscriber count |
|
|
626
|
+
| `activeUserCount` | `number` | Active users |
|
|
627
|
+
| `over18` | `boolean` | NSFW flag |
|
|
628
|
+
| `createdAtDate` | `string` | Creation date |
|
|
629
|
+
|
|
630
|
+
### Composite Types
|
|
631
|
+
|
|
632
|
+
**`RedditPostWithComments`** — returned by `getPostWithComments()`:
|
|
633
|
+
|
|
634
|
+
- `post: RedditPost`
|
|
635
|
+
- `comments: RedditComment[]`
|
|
636
|
+
- `commentsPagination: PaginationInfo | null`
|
|
637
|
+
|
|
638
|
+
**`SubredditWithPosts`** — returned by `getSubredditWithPosts()`:
|
|
639
|
+
|
|
640
|
+
- `subreddit: RedditSubreddit`
|
|
641
|
+
- `posts: RedditPost[]`
|
|
642
|
+
- `postsPagination: PaginationInfo | null`
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## Environment Variables
|
|
647
|
+
|
|
648
|
+
| Variable | Description | Default |
|
|
649
|
+
| ----------------- | -------------------------- | -------------------------- |
|
|
650
|
+
| `XPOZ_API_KEY` | API key for authentication | — |
|
|
651
|
+
| `XPOZ_SERVER_URL` | MCP server URL | `https://mcp.xpoz.ai/mcp` |
|
|
652
|
+
|
|
653
|
+
## Testing
|
|
654
|
+
|
|
655
|
+
Tests hit the live Xpoz API and require a valid API key:
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
XPOZ_API_KEY=your-api-key npx vitest run
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
## License
|
|
662
|
+
|
|
663
|
+
MIT
|