x-developer 2.6.1 → 2.6.3
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +24 -0
- package/README.md +34 -332
- package/commands/post.md +9 -3
- package/commands/trending.md +4 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/check-versions.mjs +5 -2
- package/scripts/release-guard/content-policy.mjs +5 -3
- package/scripts/release-guard/policy.mjs +1 -1
- package/server.json +1 -1
- package/skills/x-twitter-scraper/SKILL.md +18 -5
- package/skills/x-twitter-scraper/metadata.json +1 -1
- package/skills/x-twitter-scraper/references/api-endpoints-api-keys.md +6 -23
- package/skills/x-twitter-scraper/references/api-endpoints-drafts.md +20 -1
- package/skills/x-twitter-scraper/references/api-endpoints-draws.md +26 -0
- package/skills/x-twitter-scraper/references/api-endpoints-error-codes.md +30 -1
- package/skills/x-twitter-scraper/references/api-endpoints-events.md +3 -1
- package/skills/x-twitter-scraper/references/api-endpoints-extractions.md +41 -26
- package/skills/x-twitter-scraper/references/api-endpoints-monitors.md +43 -6
- package/skills/x-twitter-scraper/references/api-endpoints-radar.md +2 -1
- package/skills/x-twitter-scraper/references/api-endpoints-support.md +37 -31
- package/skills/x-twitter-scraper/references/api-endpoints-trends.md +9 -1
- package/skills/x-twitter-scraper/references/api-endpoints-tweet-style-cache.md +32 -1
- package/skills/x-twitter-scraper/references/api-endpoints-webhooks.md +55 -9
- package/skills/x-twitter-scraper/references/api-endpoints-x-accounts.md +11 -0
- package/skills/x-twitter-scraper/references/api-endpoints-x-api.md +75 -14
- package/skills/x-twitter-scraper/references/api-endpoints-x-media.md +10 -0
- package/skills/x-twitter-scraper/references/api-endpoints-x-write.md +85 -19
- package/skills/x-twitter-scraper/references/extractions.md +74 -6
- package/skills/x-twitter-scraper/references/mcp-setup.md +7 -3
- package/skills/x-twitter-scraper/references/mcp-tools.md +15 -5
- package/skills/x-twitter-scraper/references/monitor-twitter-webhooks.md +12 -10
- package/skills/x-twitter-scraper/references/python-examples.md +39 -12
- package/skills/x-twitter-scraper/references/scrape-export-twitter-data.md +8 -4
- package/skills/x-twitter-scraper/references/track-twitter-keywords-mentions.md +5 -4
- package/skills/x-twitter-scraper/references/twitter-data-pipeline.md +1 -1
- package/skills/x-twitter-scraper/references/types-error.md +24 -3
- package/skills/x-twitter-scraper/references/types-events.md +5 -25
- package/skills/x-twitter-scraper/references/types-extractions.md +49 -2
- package/skills/x-twitter-scraper/references/types-mcp-output-schemas.md +10 -19
- package/skills/x-twitter-scraper/references/types-monitors.md +30 -1
- package/skills/x-twitter-scraper/references/types-radar.md +7 -0
- package/skills/x-twitter-scraper/references/types-rest-api-vs-mcp-field-naming.md +8 -12
- package/skills/x-twitter-scraper/references/types-support.md +31 -10
- package/skills/x-twitter-scraper/references/types-trends.md +3 -0
- package/skills/x-twitter-scraper/references/types-webhooks.md +10 -2
- package/skills/x-twitter-scraper/references/types-x-articles.md +27 -12
- package/skills/x-twitter-scraper/references/types-x-write.md +46 -7
- package/skills/x-twitter-scraper/references/types.md +0 -17
- package/skills/x-twitter-scraper/references/webhooks.md +123 -35
- package/skills/x-twitter-scraper/references/workflows.md +31 -9
- package/skills/x-twitter-scraper/skill-card.md +1 -1
- package/skills/xquik-social-research/SKILL.md +6 -0
- package/stub-server.mjs +1 -1
- package/task-guides/check-mutuals.md +1 -1
- package/task-guides/clean-followers.md +1 -1
- package/task-guides/export-tweets-csv.md +14 -5
- package/task-guides/extract-followers.md +2 -2
- package/task-guides/find-bangers.md +4 -2
- package/task-guides/find-influencers.md +7 -4
- package/task-guides/find-viral-tweets.md +1 -1
- package/task-guides/follow-unfollow.md +9 -4
- package/task-guides/for-you-feed.md +1 -1
- package/task-guides/going-viral.md +1 -1
- package/task-guides/grow-followers.md +4 -2
- package/task-guides/monitor-accounts.md +8 -9
- package/task-guides/optimize-tweets.md +1 -1
- package/task-guides/post-tweets.md +10 -6
- package/task-guides/run-giveaway.md +1 -1
- package/task-guides/search-tweets.md +19 -8
- package/task-guides/send-dms.md +16 -7
- package/task-guides/top-replies.md +1 -1
- package/task-guides/track-competitors.md +2 -2
- package/task-guides/track-hashtags.md +7 -8
- package/task-guides/track-mentions.md +10 -9
- package/task-guides/trending-news.md +13 -8
- package/task-guides/tweet-analytics.md +2 -2
- package/task-guides/tweet-ideas.md +1 -1
- package/task-guides/tweet-replies.md +29 -12
- package/task-guides/tweet-style.md +1 -1
- package/task-guides/tweet-webhooks.md +16 -12
- package/task-guides/update-x-profile.md +8 -5
- package/task-guides/user-tweets.md +20 -9
- package/task-guides/who-liked.md +1 -1
- package/task-guides/who-quoted.md +1 -1
- package/task-guides/who-retweeted.md +1 -1
- package/task-guides/write-threads.md +1 -1
- package/task-guides/write-tweets.md +1 -1
- package/task-guides/x-articles.md +10 -5
- package/task-guides/x-bookmarks.md +1 -1
- package/task-guides/x-communities.md +1 -1
- package/task-guides/x-lists.md +1 -1
- package/task-guides/x-spaces.md +1 -1
- package/task-guides/x-trends.md +6 -6
- package/skills/x-twitter-scraper/references/types-mcp-add-webhook.md +0 -14
- package/skills/x-twitter-scraper/references/types-mcp-check-follow.md +0 -10
- package/skills/x-twitter-scraper/references/types-mcp-compose-tweet.md +0 -31
- package/skills/x-twitter-scraper/references/types-mcp-estimate-extraction.md +0 -15
- package/skills/x-twitter-scraper/references/types-mcp-get-account.md +0 -26
- package/skills/x-twitter-scraper/references/types-mcp-get-draw.md +0 -30
- package/skills/x-twitter-scraper/references/types-mcp-get-events.md +0 -19
- package/skills/x-twitter-scraper/references/types-mcp-get-trends.md +0 -16
- package/skills/x-twitter-scraper/references/types-mcp-get-user-info.md +0 -16
- package/skills/x-twitter-scraper/references/types-mcp-list-monitors.md +0 -15
- package/skills/x-twitter-scraper/references/types-mcp-lookup-tweet.md +0 -25
- package/skills/x-twitter-scraper/references/types-mcp-refine-tweet.md +0 -13
- package/skills/x-twitter-scraper/references/types-mcp-run-draw.md +0 -18
- package/skills/x-twitter-scraper/references/types-mcp-run-extraction.md +0 -12
- package/skills/x-twitter-scraper/references/types-mcp-score-tweet.md +0 -16
- package/skills/x-twitter-scraper/references/types-mcp-search-tweets.md +0 -17
- package/skills/x-twitter-scraper/references/types-mcp-test-webhook.md +0 -11
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
23 bulk data extraction tools. Each requires a specific target parameter.
|
|
4
4
|
|
|
5
|
+
## Privacy and Acceptable Use
|
|
6
|
+
|
|
7
|
+
Bulk extraction and export can collect large amounts of public identity,
|
|
8
|
+
activity, and relationship data. Before creating a job, confirm the lawful
|
|
9
|
+
purpose, target, `resultsLimit`, intended recipients, and retention period.
|
|
10
|
+
Follow X rules and applicable privacy law. Do not use these tools for
|
|
11
|
+
credential collection, private data, surveillance, discrimination, harassment,
|
|
12
|
+
doxxing, or unrelated secondary use. Delete exported data when the approved
|
|
13
|
+
purpose ends.
|
|
14
|
+
|
|
15
|
+
Every extraction requires an estimate and explicit approval for the exact
|
|
16
|
+
bounded job. Never infer approval from a general request or increase a bound
|
|
17
|
+
without renewed approval.
|
|
18
|
+
|
|
19
|
+
The API accepts an omitted `resultsLimit`. This Skill must always send an
|
|
20
|
+
explicit finite positive bound. Use the same bound for estimate and create.
|
|
21
|
+
|
|
5
22
|
**Endpoint:** `POST /extractions`
|
|
6
23
|
|
|
7
24
|
**Always estimate first:** `POST /extractions/estimate` with the same body to preview `creditsRequired`, `creditsAvailable`, and whether the job is allowed.
|
|
@@ -55,7 +72,7 @@
|
|
|
55
72
|
|
|
56
73
|
The `@` prefix is automatically stripped if included.
|
|
57
74
|
|
|
58
|
-
### User
|
|
75
|
+
### User Timeline Tools (require `targetUsername`)
|
|
59
76
|
|
|
60
77
|
| Tool Type | Description |
|
|
61
78
|
|-----------|-------------|
|
|
@@ -66,7 +83,7 @@ The `@` prefix is automatically stripped if included.
|
|
|
66
83
|
```json
|
|
67
84
|
{
|
|
68
85
|
"toolType": "user_likes",
|
|
69
|
-
"
|
|
86
|
+
"targetUsername": "elonmusk"
|
|
70
87
|
}
|
|
71
88
|
```
|
|
72
89
|
|
|
@@ -122,7 +139,7 @@ The `@` prefix is automatically stripped if included.
|
|
|
122
139
|
| Tool Type | Description |
|
|
123
140
|
|-----------|-------------|
|
|
124
141
|
| `people_search` | Search for users by keyword |
|
|
125
|
-
| `tweet_search_extractor` | Search and extract tweets by keyword or hashtag
|
|
142
|
+
| `tweet_search_extractor` | Search and extract tweets by keyword or hashtag |
|
|
126
143
|
|
|
127
144
|
**Example (people search):**
|
|
128
145
|
```json
|
|
@@ -143,7 +160,8 @@ The `@` prefix is automatically stripped if included.
|
|
|
143
160
|
|
|
144
161
|
### Tweet Search Filters
|
|
145
162
|
|
|
146
|
-
|
|
163
|
+
`tweet_search_extractor` accepts structured filters. It combines them with
|
|
164
|
+
`searchQuery` before collection.
|
|
147
165
|
|
|
148
166
|
| Field | Type | Description |
|
|
149
167
|
|-------|------|-------------|
|
|
@@ -153,15 +171,49 @@ The `tweet_search_extractor` tool type supports 16 additional filter fields that
|
|
|
153
171
|
| `language` | string | Language code (e.g., `en`) |
|
|
154
172
|
| `sinceDate` | string | Start date (YYYY-MM-DD) |
|
|
155
173
|
| `untilDate` | string | End date (YYYY-MM-DD) |
|
|
156
|
-
| `mediaType` | string | `images`, `videos`, `gifs`, or `
|
|
174
|
+
| `mediaType` | string | `images`, `videos`, `gifs`, `media`, `links`, or `none` |
|
|
157
175
|
| `minFaves` | number | Minimum likes |
|
|
158
176
|
| `minRetweets` | number | Minimum retweets |
|
|
159
177
|
| `minReplies` | number | Minimum replies |
|
|
178
|
+
| `minQuotes` | number | Minimum quote count |
|
|
179
|
+
| `minViews` | number | Minimum view count |
|
|
180
|
+
| `minBookmarks` | number | Minimum bookmark count |
|
|
181
|
+
| `maxLikes` | number | Maximum likes |
|
|
182
|
+
| `maxRetweets` | number | Maximum reposts |
|
|
183
|
+
| `maxReplies` | number | Maximum replies |
|
|
184
|
+
| `maxQuotes` | number | Maximum quotes |
|
|
185
|
+
| `blueVerifiedOnly` | boolean | Blue-verified authors only |
|
|
186
|
+
| `cardName` | string | Match the Tweet card name |
|
|
187
|
+
| `source` | string | Match the source application |
|
|
188
|
+
| `excludeSource` | string | Exclude a source application |
|
|
189
|
+
| `geocode` | string | Match latitude, longitude, and radius |
|
|
190
|
+
| `sinceId` | string | Tweets newer than this ID |
|
|
191
|
+
| `maxId` | string | Tweets older than this ID |
|
|
192
|
+
| `near` | string | Match a place name |
|
|
193
|
+
| `within` | string | Radius for the `near` filter |
|
|
194
|
+
| `withinTime` | string | Recent time window |
|
|
195
|
+
| `nativeRetweets` | boolean | Native reposts only |
|
|
196
|
+
| `safe` | boolean | Enable safe search |
|
|
197
|
+
| `news` | boolean | News results only |
|
|
160
198
|
| `verifiedOnly` | boolean | Verified authors only |
|
|
161
199
|
| `replies` | string | `include`, `exclude`, or `only` |
|
|
162
200
|
| `retweets` | string | `include`, `exclude`, or `only` |
|
|
201
|
+
| `quotes` | string | `include`, `exclude`, or `only` |
|
|
163
202
|
| `exactPhrase` | string | Exact match text |
|
|
164
203
|
| `excludeWords` | string | Comma-separated words to exclude |
|
|
204
|
+
| `anyWords` | string | Terms where any one can match |
|
|
205
|
+
| `hashtags` | string | Hashtags separated by spaces, commas, or lines |
|
|
206
|
+
| `cashtags` | string | Cashtags separated by spaces, commas, or lines |
|
|
207
|
+
| `url` | string | URL substring or domain |
|
|
208
|
+
| `conversationId` | string | Conversation ID |
|
|
209
|
+
| `inReplyToTweetId` | string | Replies to one Tweet ID |
|
|
210
|
+
| `quotesOfTweetId` | string | Quotes of one Tweet ID |
|
|
211
|
+
| `retweetsOfTweetId` | string | Reposts of one Tweet ID |
|
|
212
|
+
| `listId` | string | Search within a list |
|
|
213
|
+
| `place` | string | Search within a place ID |
|
|
214
|
+
| `placeCountry` | string | Search within a country code |
|
|
215
|
+
| `pointRadius` | string | Geographic point and radius |
|
|
216
|
+
| `boundingBox` | string | Geographic bounding box |
|
|
165
217
|
| `advancedQuery` | string | Raw X search operators appended to query |
|
|
166
218
|
|
|
167
219
|
**Example with filters:**
|
|
@@ -179,6 +231,13 @@ The `tweet_search_extractor` tool type supports 16 additional filter fields that
|
|
|
179
231
|
|
|
180
232
|
`resultsLimit` (optional): Maximum results to extract. Stops early instead of fetching all. Pass this on both `POST /extractions/estimate` and `POST /extractions` when you only need a specific count.
|
|
181
233
|
|
|
234
|
+
### Profile Filters
|
|
235
|
+
|
|
236
|
+
Profile-producing extractions also accept `minFollowers`, `maxFollowers`,
|
|
237
|
+
`minFollowing`, `maxFollowing`, `minPosts`, `maxPosts`,
|
|
238
|
+
`minAccountAgeDays`, `verifiedType`, `hasWebsite`, `hasLocation`,
|
|
239
|
+
`bioContains`, `locationContains`, and `usernameContains`.
|
|
240
|
+
|
|
182
241
|
## Response
|
|
183
242
|
|
|
184
243
|
```json
|
|
@@ -214,6 +273,14 @@ Formats: `csv`, `json`, `md`, `md-document`, `pdf`, `txt`, `xlsx`. 100,000 row l
|
|
|
214
273
|
|
|
215
274
|
Exports include enrichment columns not present in the API response.
|
|
216
275
|
|
|
276
|
+
The endpoint supports follower, following, post, engagement, profile, media,
|
|
277
|
+
language, search, and date filters. It does not project individual fields.
|
|
278
|
+
|
|
279
|
+
**Approval required:** Set the smallest approved `resultsLimit` when creating
|
|
280
|
+
the job. Before export, show the job, filters, format, row count, schema,
|
|
281
|
+
recipients, storage, and retention. Materialize or transmit the dataset only
|
|
282
|
+
after explicit approval. Delete it when the approved purpose ends.
|
|
283
|
+
|
|
217
284
|
## Estimating Usage
|
|
218
285
|
|
|
219
286
|
```
|
|
@@ -232,6 +299,7 @@ Same body as create. Response:
|
|
|
232
299
|
}
|
|
233
300
|
```
|
|
234
301
|
|
|
235
|
-
If `allowed` is `false`, the extraction requires more credits
|
|
302
|
+
If `allowed` is `false`, do not create the extraction. It requires more credits
|
|
303
|
+
than are currently available.
|
|
236
304
|
|
|
237
305
|
For common mistakes and tool selection rules, see [mcp-tools.md](mcp-tools.md#common-mistakes).
|
|
@@ -10,7 +10,8 @@ API keys.
|
|
|
10
10
|
| Protocol | Streamable HTTP |
|
|
11
11
|
| Endpoint | `https://xquik.com/mcp` |
|
|
12
12
|
| Authentication | OAuth 2.1 discovery; API key fallback |
|
|
13
|
-
|
|
|
13
|
+
| Hosted MCP version | `2.6.0` |
|
|
14
|
+
| Skill bundle version | `2.6.3` |
|
|
14
15
|
|
|
15
16
|
Current clients negotiate MCP `2026-07-28` through `server/discover`.
|
|
16
17
|
Use a current MCP SDK. It adds request `_meta` and protocol headers.
|
|
@@ -287,7 +288,8 @@ Active guest `paid_reads` keys expose 33 eligible GET routes.
|
|
|
287
288
|
|
|
288
289
|
## MCP Server Architecture
|
|
289
290
|
|
|
290
|
-
|
|
291
|
+
Hosted MCP v2.6.0 exposes 120 catalog routes through 2 structured API tools.
|
|
292
|
+
Of these, 119 support JSON or text. Binary support downloads use REST.
|
|
291
293
|
|
|
292
294
|
| Tool | Description | Usage |
|
|
293
295
|
|------|-------------|------|
|
|
@@ -298,7 +300,7 @@ The MCP server (v2.6.1) exposes 120 catalog routes through 2 structured API tool
|
|
|
298
300
|
operations with normalized snake_case responses. Authentication is injected, so
|
|
299
301
|
tool code must never include credentials.
|
|
300
302
|
|
|
301
|
-
MCP v2.6.
|
|
303
|
+
Hosted MCP v2.6.0 catalogs 120 of 128 documented REST operations. These 8 credential,
|
|
302
304
|
checkout, or guest-wallet operations remain direct REST or dashboard workflows:
|
|
303
305
|
|
|
304
306
|
- API key creation, listing, and revocation
|
|
@@ -327,6 +329,8 @@ Handle failures from structured error fields:
|
|
|
327
329
|
|
|
328
330
|
- `401`: reconnect OAuth or replace the revoked API key.
|
|
329
331
|
- `402`: report payment options. Never create checkout without confirmation.
|
|
332
|
+
- `409 coverage_cursor_unavailable`: wait the exact `Retry-After` seconds, then retry the same cursor once.
|
|
333
|
+
- `410 coverage_cursor_gone`: no `Retry-After`; restart without a cursor and deduplicate by ID.
|
|
330
334
|
- `429`: honor `Retry-After`.
|
|
331
335
|
- `5xx`: retry read-only requests with bounded exponential backoff.
|
|
332
336
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The MCP server at `https://xquik.com/mcp` provides 2 structured API tools. The agent sends API requests through the server, which handles authentication and execution for `xquik.com/api/v1`.
|
|
4
4
|
|
|
5
|
-
MCP v2.6.
|
|
5
|
+
Hosted MCP v2.6.0 supports `2026-07-28` through `server/discover`.
|
|
6
6
|
Current MCP SDKs add request metadata and headers automatically.
|
|
7
7
|
Modern calls need no initialization session.
|
|
8
8
|
|
|
@@ -35,6 +35,7 @@ interface EndpointInfo {
|
|
|
35
35
|
category: string; // account, composition, credits, extraction, media, monitoring, support, twitter, x-accounts, x-write
|
|
36
36
|
free: boolean; // Included usage flag from endpoint metadata
|
|
37
37
|
parameters?: Array<{ name: string; in: 'query' | 'path' | 'body'; required: boolean; type: string; description: string }>;
|
|
38
|
+
injectedHeaders?: string[];
|
|
38
39
|
responseShape?: string;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -56,7 +57,11 @@ async () => spec.endpoints.filter(e => e.summary.toLowerCase().includes('tweet')
|
|
|
56
57
|
|
|
57
58
|
### `xquik` - Send API Requests
|
|
58
59
|
|
|
59
|
-
The tool provides `xquik.request()` with
|
|
60
|
+
The tool provides `xquik.request()` with authentication and required idempotency headers injected automatically. Never pass API keys or headers. The sandbox reuses each generated key for bounded transient retries. After an unresolved write failure, verify state. Start a new attempt only when `safe_to_retry` is true and the user approves.
|
|
61
|
+
|
|
62
|
+
For `409 coverage_cursor_unavailable`, wait the exact `Retry-After` seconds and
|
|
63
|
+
retry the same cursor once. For `410 coverage_cursor_gone`, the response omits
|
|
64
|
+
`Retry-After`. Restart without a cursor and deduplicate by ID.
|
|
60
65
|
|
|
61
66
|
## Safety Gates
|
|
62
67
|
|
|
@@ -128,11 +133,16 @@ Use `explore` first to find endpoints, then `xquik` to call them.
|
|
|
128
133
|
| Get bookmark folders | `GET /api/v1/x/bookmarks/folders` |
|
|
129
134
|
| Get notifications | `GET /api/v1/x/notifications` (private; confirmation required) |
|
|
130
135
|
| Get home timeline | `GET /api/v1/x/timeline` (private; confirmation required) |
|
|
131
|
-
| Get DM history | `GET /api/v1/x/dm/{userId}/history` (private;
|
|
136
|
+
| Get DM history | `GET /api/v1/x/dm/{userId}/history?account={username}` (private; exact-account approval required; block ambiguous selection) |
|
|
132
137
|
| Check credit balance | `GET /api/v1/credits` |
|
|
133
138
|
|
|
134
139
|
Use `POST /api/v1/extractions` ONLY for bulk data that simpler endpoints cannot provide (all followers, all replies to a tweet, community members, etc.). Always call `POST /api/v1/extractions/estimate` first.
|
|
135
140
|
|
|
141
|
+
Fresh cursorless Tweet Search with `queryType=Latest` is newest-first across
|
|
142
|
+
pages. Existing cursors retain their ordering. Thread reads accept 32 effective
|
|
143
|
+
result filters. They exclude `nativeRetweets`, `sinceTime`, and `untilTime`.
|
|
144
|
+
See [direct lookups](api-endpoints-x-api.md) for the exact names.
|
|
145
|
+
|
|
136
146
|
## Workflow Patterns
|
|
137
147
|
|
|
138
148
|
| Workflow | Steps |
|
|
@@ -142,7 +152,7 @@ Use `POST /api/v1/extractions` ONLY for bulk data that simpler endpoints cannot
|
|
|
142
152
|
| **Bulk extraction** | `POST /extractions/estimate` -> `POST /extractions` -> `GET /extractions/{id}` |
|
|
143
153
|
| **Compose optimized tweet** | `POST /compose` (step=compose -> refine -> score) |
|
|
144
154
|
| **Analyze tweet style** | `POST /styles` -> `GET /styles/{id}` -> `POST /compose` with `styleUsername` |
|
|
145
|
-
| **Post a tweet** | `GET /x/accounts` -> `POST /x/tweets` with `account`
|
|
155
|
+
| **Post a tweet** | `GET /x/accounts` -> confirm -> `POST /x/tweets` with `account` and `text` -> hosted MCP injects a unique `Idempotency-Key` -> poll `statusUrl` |
|
|
146
156
|
| **Get trending news** | `GET /radar` (supported sources, via `xquik` tool) -> `POST /compose` with trending topic |
|
|
147
157
|
| **Open support ticket** | `POST /support/tickets` -> `GET /support/tickets/{id}` |
|
|
148
158
|
| **Collect maximum-coverage replies** | `GET /x/tweets/{id}/replies?mode=complete&limit=<1-25000>` -> filter direct rows by `inReplyToId` -> keep `nested_replies` separate -> inspect `diagnostic` |
|
|
@@ -164,7 +174,7 @@ Use `POST /api/v1/extractions` ONLY for bulk data that simpler endpoints cannot
|
|
|
164
174
|
|
|
165
175
|
## REST-only operations
|
|
166
176
|
|
|
167
|
-
MCP v2.6.
|
|
177
|
+
Hosted MCP v2.6.0 catalogs 120 of 128 REST operations.
|
|
168
178
|
Of these, 119 support JSON or text. Binary support downloads use REST.
|
|
169
179
|
These 8 credential, checkout, or guest-wallet operations remain outside MCP:
|
|
170
180
|
|
|
@@ -11,9 +11,10 @@ them to an HTTPS endpoint through HMAC-signed webhooks.
|
|
|
11
11
|
|
|
12
12
|
| Object | Fields to Preserve |
|
|
13
13
|
| --- | --- |
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
14
|
+
| Account monitor | Monitor ID, username, event types, active state |
|
|
15
|
+
| Keyword monitor | Monitor ID, query, event types, active state |
|
|
16
|
+
| Event | Event ID, monitor ID and type, event type, occurrence time, data |
|
|
17
|
+
| Delivery | Delivery ID, stream event ID, attempts, status, delivery time |
|
|
17
18
|
|
|
18
19
|
## Twitter Monitor Polling Versus Webhook Delivery
|
|
19
20
|
|
|
@@ -21,7 +22,7 @@ them to an HTTPS endpoint through HMAC-signed webhooks.
|
|
|
21
22
|
| --- | --- | --- |
|
|
22
23
|
| Simple scheduled batch | Strong fit | Optional |
|
|
23
24
|
| Low detection delay | More frequent polling needed | Strong fit |
|
|
24
|
-
|
|
|
25
|
+
| Recover after downtime | Resume with the stored cursor | Inspect delivery status, then repoll events |
|
|
25
26
|
| Public HTTPS endpoint | Not required | Required |
|
|
26
27
|
| Signature verification | Not applicable | Required |
|
|
27
28
|
| Backpressure control | Caller controls fetch rate | Receiver must queue work |
|
|
@@ -54,8 +55,9 @@ Webhook alerts are HTTPS POST requests sent when a monitor creates a matching
|
|
|
54
55
|
event. Xquik signs each delivery with a per-webhook HMAC secret. The secret is
|
|
55
56
|
shown only once and should enter a secret store immediately.
|
|
56
57
|
|
|
57
|
-
Verify
|
|
58
|
-
|
|
58
|
+
Verify `<timestamp>.<nonce>.<raw body>` with `X-Xquik-Timestamp`,
|
|
59
|
+
`X-Xquik-Nonce`, and `X-Xquik-Signature`. Reject timestamps outside 5 minutes
|
|
60
|
+
and reused nonces. Reject invalid signatures before parsing business data.
|
|
59
61
|
|
|
60
62
|
### What is a Twitter account monitor API?
|
|
61
63
|
|
|
@@ -94,7 +96,7 @@ from production analytics.
|
|
|
94
96
|
1. Read the raw request body.
|
|
95
97
|
2. Compute and compare the expected HMAC signature safely.
|
|
96
98
|
3. Reject invalid or missing signatures.
|
|
97
|
-
4. Deduplicate
|
|
99
|
+
4. Deduplicate webhook attempts by `deliveryId`.
|
|
98
100
|
5. Acknowledge valid delivery quickly.
|
|
99
101
|
6. Process asynchronously with bounded retries.
|
|
100
102
|
7. Record attempt, status, and failure reason.
|
|
@@ -107,9 +109,9 @@ plan changes, credit changes, or tool changes.
|
|
|
107
109
|
|
|
108
110
|
1. Pause downstream actions when signature checks fail.
|
|
109
111
|
2. Check monitor status and delivery history.
|
|
110
|
-
3. Restore the receiver before
|
|
111
|
-
4.
|
|
112
|
-
5. Deduplicate
|
|
112
|
+
3. Restore the receiver before resuming delivery.
|
|
113
|
+
4. Repoll events from the last stored cursor when recovery needs them.
|
|
114
|
+
5. Deduplicate event IDs and webhook `deliveryId` values.
|
|
113
115
|
6. Compare source and stored timestamps.
|
|
114
116
|
7. Document gaps and permanent failures.
|
|
115
117
|
|
|
@@ -12,6 +12,12 @@ Python equivalents of the JavaScript examples in SKILL.md.
|
|
|
12
12
|
|
|
13
13
|
## Authentication
|
|
14
14
|
|
|
15
|
+
> **External transmission:** These examples send credentials, parameters, and
|
|
16
|
+
> returned data to and from `xquik.com`. Keep the key in a secret store. Get
|
|
17
|
+
> explicit approval before private reads, writes, exports, persistent resources,
|
|
18
|
+
> webhooks, or metered jobs. Never forward private results without separate
|
|
19
|
+
> approval.
|
|
20
|
+
|
|
15
21
|
```python
|
|
16
22
|
import json
|
|
17
23
|
import urllib.error
|
|
@@ -33,6 +39,8 @@ import time, random
|
|
|
33
39
|
|
|
34
40
|
def xquik_fetch(path, method="GET", json_body=None, max_retries=3):
|
|
35
41
|
base_delay = 1.0
|
|
42
|
+
method = method.upper()
|
|
43
|
+
retry_safe = method in {"GET", "HEAD", "OPTIONS"}
|
|
36
44
|
|
|
37
45
|
for attempt in range(max_retries + 1):
|
|
38
46
|
retry_after = None
|
|
@@ -49,7 +57,7 @@ def xquik_fetch(path, method="GET", json_body=None, max_retries=3):
|
|
|
49
57
|
payload = json.loads(error.read() or b"{}")
|
|
50
58
|
retry_after = error.headers.get("Retry-After")
|
|
51
59
|
|
|
52
|
-
retryable = status == 429 or status >= 500
|
|
60
|
+
retryable = retry_safe and (status == 429 or status >= 500)
|
|
53
61
|
if not retryable or attempt == max_retries:
|
|
54
62
|
raise Exception(f"Xquik API {status}: {payload.get('error', 'request failed')}")
|
|
55
63
|
|
|
@@ -60,10 +68,18 @@ def xquik_fetch(path, method="GET", json_body=None, max_retries=3):
|
|
|
60
68
|
## Extraction Workflow
|
|
61
69
|
|
|
62
70
|
```python
|
|
71
|
+
RESULTS_LIMIT = 1000
|
|
72
|
+
|
|
73
|
+
def require_explicit_approval(scope: str) -> None:
|
|
74
|
+
raise RuntimeError(
|
|
75
|
+
f"Approval required for {scope}. Implement the approval gate first."
|
|
76
|
+
)
|
|
77
|
+
|
|
63
78
|
# Step 1: Estimate
|
|
64
79
|
estimate = xquik_fetch("/extractions/estimate", method="POST", json_body={
|
|
65
80
|
"toolType": "reply_extractor",
|
|
66
81
|
"targetTweetId": "1893704267862470862",
|
|
82
|
+
"resultsLimit": RESULTS_LIMIT,
|
|
67
83
|
})
|
|
68
84
|
|
|
69
85
|
if not estimate["allowed"]:
|
|
@@ -71,9 +87,13 @@ if not estimate["allowed"]:
|
|
|
71
87
|
exit()
|
|
72
88
|
|
|
73
89
|
# Step 2: Create job
|
|
90
|
+
require_explicit_approval(
|
|
91
|
+
"the bounded extraction job, usage, recipients, and retention"
|
|
92
|
+
)
|
|
74
93
|
job = xquik_fetch("/extractions", method="POST", json_body={
|
|
75
94
|
"toolType": "reply_extractor",
|
|
76
95
|
"targetTweetId": "1893704267862470862",
|
|
96
|
+
"resultsLimit": RESULTS_LIMIT,
|
|
77
97
|
})
|
|
78
98
|
|
|
79
99
|
# Step 3: Poll until complete (large jobs may return "running")
|
|
@@ -88,7 +108,7 @@ results = []
|
|
|
88
108
|
while True:
|
|
89
109
|
path = f"/extractions/{job['id']}"
|
|
90
110
|
if cursor:
|
|
91
|
-
path += f"?
|
|
111
|
+
path += f"?cursor={cursor}"
|
|
92
112
|
page = xquik_fetch(path)
|
|
93
113
|
results.extend(page["results"])
|
|
94
114
|
|
|
@@ -128,6 +148,8 @@ for winner in details["winners"]:
|
|
|
128
148
|
import hashlib
|
|
129
149
|
import hmac
|
|
130
150
|
import json
|
|
151
|
+
import re
|
|
152
|
+
import time
|
|
131
153
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
132
154
|
|
|
133
155
|
def load_secret(name: str) -> str:
|
|
@@ -136,10 +158,15 @@ def load_secret(name: str) -> str:
|
|
|
136
158
|
|
|
137
159
|
# Per-webhook secret from POST /webhooks response, not a Xquik account credential
|
|
138
160
|
WEBHOOK_SECRET = load_secret("XQUIK_WEBHOOK_SECRET")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
|
|
142
|
-
|
|
161
|
+
processed_delivery_ids = set() # Use durable storage in production
|
|
162
|
+
|
|
163
|
+
def verify_signature(payload: bytes, signature: str, timestamp: str, nonce: str, secret: str) -> bool:
|
|
164
|
+
if not timestamp.isdigit() or not re.fullmatch(r"[0-9a-f]{32}", nonce):
|
|
165
|
+
return False
|
|
166
|
+
if abs(int(time.time() * 1000) - int(timestamp)) > 5 * 60 * 1000:
|
|
167
|
+
return False
|
|
168
|
+
signing_input = timestamp.encode() + b"." + nonce.encode() + b"." + payload
|
|
169
|
+
expected = "sha256=" + hmac.new(secret.encode(), signing_input, hashlib.sha256).hexdigest()
|
|
143
170
|
return hmac.compare_digest(expected, signature)
|
|
144
171
|
|
|
145
172
|
EVENT_HANDLERS = {
|
|
@@ -153,23 +180,23 @@ class WebhookHandler(BaseHTTPRequestHandler):
|
|
|
153
180
|
def do_POST(self):
|
|
154
181
|
length = int(self.headers.get("Content-Length", "0"))
|
|
155
182
|
signature = self.headers.get("X-Xquik-Signature", "")
|
|
183
|
+
timestamp = self.headers.get("X-Xquik-Timestamp", "")
|
|
184
|
+
nonce = self.headers.get("X-Xquik-Nonce", "")
|
|
156
185
|
payload = self.rfile.read(length)
|
|
157
186
|
|
|
158
|
-
if not verify_signature(payload, signature, WEBHOOK_SECRET):
|
|
187
|
+
if not verify_signature(payload, signature, timestamp, nonce, WEBHOOK_SECRET):
|
|
159
188
|
self.send_response(401)
|
|
160
189
|
self.end_headers()
|
|
161
190
|
self.wfile.write(b"Invalid signature")
|
|
162
191
|
return
|
|
163
192
|
|
|
164
|
-
|
|
165
|
-
if
|
|
193
|
+
event = json.loads(payload)
|
|
194
|
+
if event["deliveryId"] in processed_delivery_ids:
|
|
166
195
|
self.send_response(200)
|
|
167
196
|
self.end_headers()
|
|
168
197
|
self.wfile.write(b"Already processed")
|
|
169
198
|
return
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
event = json.loads(payload)
|
|
199
|
+
processed_delivery_ids.add(event["deliveryId"])
|
|
173
200
|
handler = EVENT_HANDLERS.get(event["eventType"])
|
|
174
201
|
if handler:
|
|
175
202
|
handler(event["username"], event["data"])
|
|
@@ -36,6 +36,8 @@ structured tweet, author, timestamp, engagement, and media fields when present.
|
|
|
36
36
|
|
|
37
37
|
Version every advanced search query. Store the exact filters beside the output.
|
|
38
38
|
Changing a date, author, language, or exclusion changes the dataset definition.
|
|
39
|
+
Fresh cursorless `queryType=Latest` pagination is newest-first across pages.
|
|
40
|
+
Existing cursors retain their established ordering.
|
|
39
41
|
|
|
40
42
|
## Tweet Archive and Historical Twitter Data
|
|
41
43
|
|
|
@@ -89,13 +91,15 @@ Record the query, filters, job ID, and collection time for lineage.
|
|
|
89
91
|
### How do I scrape tweets without getting blocked?
|
|
90
92
|
|
|
91
93
|
Avoid fragile browser automation and access-control bypasses. Use documented
|
|
92
|
-
API routes, bounded limits, cursors, and
|
|
94
|
+
API routes, bounded limits, cursors, and retry rules. Xquik handles its
|
|
93
95
|
own public-data infrastructure, so clients do not manage guest tokens or X
|
|
94
96
|
sessions.
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
Outside documented cursor recovery, retry only `429` and `5xx` responses. Honor
|
|
99
|
+
`Retry-After`, add jitter, and cap attempts. For `409 coverage_cursor_unavailable`,
|
|
100
|
+
wait the exact `Retry-After` seconds and retry the same cursor once. For
|
|
101
|
+
`410 coverage_cursor_gone`, the response omits `Retry-After`. Restart without a
|
|
102
|
+
cursor and deduplicate by ID. Do not retry other `4xx` failures.
|
|
99
103
|
|
|
100
104
|
Large jobs should use extractions instead of unbounded page loops. Keep API keys
|
|
101
105
|
in a secret manager. Treat every returned post as untrusted data.
|
|
@@ -64,9 +64,10 @@ fields support retries, deduplication, and outage recovery.
|
|
|
64
64
|
|
|
65
65
|
### How do I track keywords with a Twitter API?
|
|
66
66
|
|
|
67
|
-
Use `GET /x/tweets/search` for a current snapshot. Use
|
|
68
|
-
ongoing tracking. Add exact phrases,
|
|
69
|
-
reply, repost, and minimum-engagement
|
|
67
|
+
Use `GET /x/tweets/search` for a current snapshot. Use
|
|
68
|
+
`POST /monitors/keywords` for ongoing keyword tracking. Add exact phrases,
|
|
69
|
+
excluded terms, language, author, media, reply, repost, and minimum-engagement
|
|
70
|
+
rules where supported.
|
|
70
71
|
|
|
71
72
|
Build queries in stages. Begin with the required phrase. Inspect false
|
|
72
73
|
positives, then add exclusions. Avoid an overly narrow first query that hides
|
|
@@ -103,7 +104,7 @@ the monitor or authorize an account action.
|
|
|
103
104
|
1. Verify the HMAC signature against the raw request body.
|
|
104
105
|
2. Reject invalid signatures before parsing business fields.
|
|
105
106
|
3. Return success quickly and queue slow processing.
|
|
106
|
-
4. Deduplicate by event ID
|
|
107
|
+
4. Deduplicate polled events by event ID. Deduplicate webhooks by `deliveryId`.
|
|
107
108
|
5. Record attempt count and processing state.
|
|
108
109
|
6. Test delivery before enabling production automation.
|
|
109
110
|
7. Preserve a documented disable and delete path.
|
|
@@ -119,7 +119,7 @@ destinations, writes, or persistent resources.
|
|
|
119
119
|
| Partial export | Keep the watermark unchanged | Mark the time window complete |
|
|
120
120
|
| Schema mismatch | Quarantine the batch and alert | Drop unknown fields silently |
|
|
121
121
|
| Duplicate tweet ID | Deduplicate and record the rate | Count both rows in analytics |
|
|
122
|
-
| Webhook outage | Restore delivery
|
|
122
|
+
| Webhook outage | Restore delivery, repoll events, and deduplicate `deliveryId` | Apply repeated deliveries twice |
|
|
123
123
|
|
|
124
124
|
Track operational service-level indicators per run. Include completion rate,
|
|
125
125
|
retry rate, duplicate rate, validation failures, source-to-storage delay, and
|
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
# Xquik TypeScript Types: Error
|
|
2
2
|
|
|
3
3
|
```typescript
|
|
4
|
+
type ApiErrorCode = string;
|
|
5
|
+
|
|
6
|
+
type ApiErrorType =
|
|
7
|
+
| "api_error"
|
|
8
|
+
| "authentication_error"
|
|
9
|
+
| "billing_error"
|
|
10
|
+
| "dependency_error"
|
|
11
|
+
| "invalid_request_error"
|
|
12
|
+
| "permission_error"
|
|
13
|
+
| "rate_limit_error";
|
|
4
14
|
|
|
5
15
|
interface ApiError {
|
|
6
|
-
error:
|
|
7
|
-
|
|
16
|
+
error:
|
|
17
|
+
| ApiErrorCode
|
|
18
|
+
| { message: string; type: ApiErrorType; code: ApiErrorCode };
|
|
19
|
+
message?: string;
|
|
20
|
+
reason?: string;
|
|
21
|
+
retryAfter?: number;
|
|
22
|
+
retryAfterMs?: number;
|
|
8
23
|
}
|
|
9
|
-
|
|
10
24
|
```
|
|
25
|
+
|
|
26
|
+
OpenAPI enumerates all 109 `ApiErrorCode` values, including
|
|
27
|
+
`user_not_found`. Generated SDKs expose that exhaustive enum. The handwritten
|
|
28
|
+
type remains a string for forward compatibility.
|
|
29
|
+
|
|
30
|
+
Default v1 errors use the string form. Send
|
|
31
|
+
`xquik-api-contract: 2026-04-29` to receive the structured form.
|
|
@@ -6,34 +6,14 @@ interface XquikEvent {
|
|
|
6
6
|
id: string;
|
|
7
7
|
type: EventType;
|
|
8
8
|
monitorId: string;
|
|
9
|
-
|
|
9
|
+
monitorType: "account" | "keyword";
|
|
10
|
+
username?: string;
|
|
11
|
+
query?: string;
|
|
12
|
+
keywordMonitorId?: string;
|
|
10
13
|
occurredAt: string;
|
|
11
|
-
data:
|
|
12
|
-
xEventId?: string;
|
|
14
|
+
data: Record<string, unknown>;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
// Tweet events (tweet.new, tweet.reply, tweet.quote, tweet.retweet)
|
|
16
|
-
interface TweetEventData {
|
|
17
|
-
tweetId: string;
|
|
18
|
-
text: string;
|
|
19
|
-
metrics: {
|
|
20
|
-
likes: number;
|
|
21
|
-
retweets: number;
|
|
22
|
-
replies: number;
|
|
23
|
-
};
|
|
24
|
-
// tweet.quote only
|
|
25
|
-
quotedTweetId?: string;
|
|
26
|
-
quotedUsername?: string;
|
|
27
|
-
// tweet.reply only
|
|
28
|
-
inReplyToTweetId?: string;
|
|
29
|
-
inReplyToUsername?: string;
|
|
30
|
-
// tweet.retweet only
|
|
31
|
-
retweetedTweetId?: string;
|
|
32
|
-
retweetedUsername?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type EventData = TweetEventData;
|
|
36
|
-
|
|
37
17
|
interface EventList {
|
|
38
18
|
events: XquikEvent[];
|
|
39
19
|
hasMore: boolean;
|
|
@@ -34,7 +34,6 @@ interface ExtractionJob {
|
|
|
34
34
|
totalResults: number;
|
|
35
35
|
targetTweetId?: string;
|
|
36
36
|
targetUsername?: string;
|
|
37
|
-
targetUserId?: string;
|
|
38
37
|
targetCommunityId?: string;
|
|
39
38
|
targetListId?: string;
|
|
40
39
|
targetSpaceId?: string;
|
|
@@ -91,16 +90,64 @@ interface CreateExtractionRequest {
|
|
|
91
90
|
language?: string;
|
|
92
91
|
sinceDate?: string; // YYYY-MM-DD
|
|
93
92
|
untilDate?: string; // YYYY-MM-DD
|
|
94
|
-
mediaType?: 'images' | 'videos' | 'gifs' | 'media';
|
|
93
|
+
mediaType?: 'images' | 'videos' | 'gifs' | 'media' | 'links' | 'none';
|
|
95
94
|
minFaves?: number;
|
|
96
95
|
minRetweets?: number;
|
|
97
96
|
minReplies?: number;
|
|
97
|
+
minQuotes?: number;
|
|
98
|
+
minViews?: number;
|
|
99
|
+
minBookmarks?: number;
|
|
100
|
+
maxLikes?: number;
|
|
101
|
+
maxRetweets?: number;
|
|
102
|
+
maxReplies?: number;
|
|
103
|
+
maxQuotes?: number;
|
|
104
|
+
blueVerifiedOnly?: boolean;
|
|
105
|
+
cardName?: string;
|
|
106
|
+
source?: string;
|
|
107
|
+
excludeSource?: string;
|
|
108
|
+
geocode?: string;
|
|
109
|
+
sinceId?: string;
|
|
110
|
+
maxId?: string;
|
|
111
|
+
near?: string;
|
|
112
|
+
within?: string;
|
|
113
|
+
withinTime?: string;
|
|
114
|
+
nativeRetweets?: boolean;
|
|
115
|
+
safe?: boolean;
|
|
116
|
+
news?: boolean;
|
|
98
117
|
verifiedOnly?: boolean;
|
|
99
118
|
replies?: 'include' | 'exclude' | 'only';
|
|
100
119
|
retweets?: 'include' | 'exclude' | 'only';
|
|
120
|
+
quotes?: 'include' | 'exclude' | 'only';
|
|
101
121
|
exactPhrase?: string;
|
|
102
122
|
excludeWords?: string;
|
|
123
|
+
anyWords?: string;
|
|
124
|
+
hashtags?: string;
|
|
125
|
+
cashtags?: string;
|
|
126
|
+
url?: string;
|
|
127
|
+
conversationId?: string;
|
|
128
|
+
inReplyToTweetId?: string;
|
|
129
|
+
quotesOfTweetId?: string;
|
|
130
|
+
retweetsOfTweetId?: string;
|
|
131
|
+
listId?: string;
|
|
132
|
+
place?: string;
|
|
133
|
+
placeCountry?: string;
|
|
134
|
+
pointRadius?: string;
|
|
135
|
+
boundingBox?: string;
|
|
103
136
|
advancedQuery?: string;
|
|
137
|
+
// Profile filters for profile-producing extractions
|
|
138
|
+
minFollowers?: number;
|
|
139
|
+
maxFollowers?: number;
|
|
140
|
+
minFollowing?: number;
|
|
141
|
+
maxFollowing?: number;
|
|
142
|
+
minPosts?: number;
|
|
143
|
+
maxPosts?: number;
|
|
144
|
+
minAccountAgeDays?: number;
|
|
145
|
+
verifiedType?: string;
|
|
146
|
+
hasWebsite?: boolean;
|
|
147
|
+
hasLocation?: boolean;
|
|
148
|
+
bioContains?: string;
|
|
149
|
+
locationContains?: string;
|
|
150
|
+
usernameContains?: string;
|
|
104
151
|
}
|
|
105
152
|
|
|
106
153
|
```
|