@xquik/tweetclaw 1.2.0 → 1.3.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 CHANGED
@@ -12,14 +12,26 @@ openclaw plugins install @xquik/tweetclaw
12
12
 
13
13
  ## Configure
14
14
 
15
- 1. Get an API key at [xquik.com/account-manager](https://xquik.com/account-manager)
16
- 2. Set it in OpenClaw:
15
+ ### Option A: API key (full access, 97 endpoints)
17
16
 
18
17
  ```bash
19
18
  openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'
20
19
  ```
21
20
 
22
- Optional settings:
21
+ Get a key at [dashboard.xquik.com/en/account?tab=x-accounts](https://dashboard.xquik.com/en/account?tab=x-accounts).
22
+
23
+ ### Option B: MPP pay-per-use (no account needed, 7 read-only endpoints)
24
+
25
+ ```bash
26
+ npm i mppx viem
27
+ openclaw config set plugins.entries.tweetclaw.config.tempoPrivateKey '0xYOUR_TEMPO_KEY'
28
+ ```
29
+
30
+ MPP (Machine Payments Protocol) lets agents pay per API call via Tempo (USDC). No account, no API key, no subscription. Get a Tempo wallet at [tempo.xyz](https://tempo.xyz).
31
+
32
+ MPP-eligible endpoints: tweet lookup ($0.0003), tweet search ($0.0003/tweet), user lookup ($0.00036), follower check ($0.002), article lookup ($0.002), media download ($0.0003/media), trends ($0.0009).
33
+
34
+ ### Optional settings
23
35
 
24
36
  ```bash
25
37
  openclaw config set plugins.entries.tweetclaw.config.pollingEnabled true
@@ -85,36 +97,45 @@ You: "Monitor @elonmusk for new tweets and follower changes"
85
97
 
86
98
  ## API Coverage
87
99
 
88
- 40+ endpoints across these categories:
100
+ 97 endpoints across these categories:
89
101
 
90
102
  | Category | Examples |
91
103
  |----------|---------|
92
104
  | **Write Actions** | Post tweets, reply, like, retweet, follow, unfollow, DM, update profile & avatar |
93
105
  | **Media** | Upload media via URL, download tweet media, get gallery links |
94
- | **Twitter** | Search tweets, look up users, check follow relationships |
106
+ | **Twitter** | Search tweets, look up users, check follow relationships, get articles |
95
107
  | **Composition** | Compose, refine, score tweets; manage drafts; analyze writing styles |
96
108
  | **Extraction** | Run extraction jobs (reply-extractor, community-explorer, etc.) |
97
109
  | **Draws** | Run giveaway draws on tweets, export results |
98
110
  | **Monitoring** | Create monitors, view events, manage webhooks |
111
+ | **Automations** | Create flows, add steps, test runs, inbound webhooks |
99
112
  | **Account** | Manage API keys, subscription, connected X accounts |
100
113
  | **Trends** | X trending topics, curated radar from multiple sources |
114
+ | **Support** | Create tickets, reply, track status |
101
115
 
102
116
  ## Pricing
103
117
 
104
- **Free tier** (no subscription needed):
118
+ **MPP pay-per-use** (no account needed):
119
+ - 7 read-only X-API endpoints via Tempo (USDC)
120
+ - Tweet/user/article lookup, search, follower check, media download, trends
121
+
122
+ **Free tier** (API key, no subscription needed):
105
123
  - Tweet composition, style analysis, drafts
106
124
  - Curated trending radar
107
125
  - Account management, API keys
108
126
  - Integrations management
127
+ - Flow automations (create, test, inbound webhooks)
128
+ - Support tickets
109
129
 
110
130
  **Subscription ($20/month)** for full access:
111
131
  - Write actions (post, reply, like, retweet, follow, DM, update profile)
112
- - Tweet search, user lookup, media download
132
+ - Tweet search, user lookup, article lookup, media download
113
133
  - Extractions, giveaway draws
114
134
  - Account monitors, events, webhooks
115
135
  - X trending topics
136
+ - Flow activation (free: 2 flows, subscriber: 10)
116
137
 
117
- When a paid endpoint returns 402, TweetClaw automatically provides a checkout URL.
138
+ When a paid endpoint returns 402, TweetClaw provides a checkout URL (API key mode) or auto-pays via Tempo (MPP mode).
118
139
 
119
140
  ## License
120
141
 
@@ -1,21 +1,26 @@
1
1
  {
2
2
  "id": "tweetclaw",
3
3
  "name": "TweetClaw",
4
- "version": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "description": "Post tweets, reply, like, retweet, follow, DM from your chat - full X/Twitter automation powered by Xquik",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
9
9
  "properties": {
10
- "apiKey": { "type": "string", "minLength": 1, "description": "Xquik API key (get one at xquik.com/account-manager)" },
10
+ "apiKey": { "type": "string", "minLength": 1, "description": "Xquik API key (get one at dashboard.xquik.com/en/account?tab=x-accounts). Required for full access." },
11
+ "tempoPrivateKey": { "type": "string", "minLength": 1, "description": "Tempo wallet private key for MPP pay-per-use mode. No account needed. Only 7 read-only X-API endpoints." },
11
12
  "baseUrl": { "type": "string", "default": "https://xquik.com" },
12
13
  "pollingInterval": { "type": "number", "default": 60, "description": "Event polling interval in seconds" },
13
14
  "pollingEnabled": { "type": "boolean", "default": true }
14
15
  },
15
- "required": ["apiKey"]
16
+ "anyOf": [
17
+ { "required": ["apiKey"] },
18
+ { "required": ["tempoPrivateKey"] }
19
+ ]
16
20
  },
17
21
  "uiHints": {
18
- "apiKey": { "label": "Xquik API Key", "sensitive": true, "placeholder": "xq_...", "help": "Generate at xquik.com/account-manager. Required for all API calls." },
22
+ "apiKey": { "label": "Xquik API Key", "sensitive": true, "placeholder": "xq_...", "help": "Full access to all 97 endpoints. Generate at dashboard.xquik.com/en/account?tab=x-accounts." },
23
+ "tempoPrivateKey": { "label": "Tempo Private Key (MPP)", "sensitive": true, "placeholder": "0x...", "help": "Pay-per-use mode via Machine Payments Protocol. No account needed. 7 read-only X-API endpoints only. Requires mppx and viem packages." },
19
24
  "baseUrl": { "label": "API Base URL", "placeholder": "https://xquik.com", "advanced": true, "help": "Only change if using a self-hosted Xquik instance." },
20
25
  "pollingInterval": { "label": "Event Poll Interval (seconds)", "advanced": true, "help": "How often to check for new monitor events. Default: 60 seconds." },
21
26
  "pollingEnabled": { "label": "Enable Event Notifications", "advanced": true, "help": "Deliver monitor alerts and extraction completions to your chat." }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xquik/tweetclaw",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Post tweets, reply, like, retweet, follow, DM & more from OpenClaw - full X/Twitter automation via Xquik",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,7 +45,13 @@
45
45
  "check:all": "npm run typecheck && npm run lint && npm run cpd && npm run knip && npm run check-suppressions && npm run check-em-dash && npm run test:coverage"
46
46
  },
47
47
  "peerDependencies": {
48
- "openclaw": ">=2026.2.0"
48
+ "mppx": ">=0.1.0",
49
+ "openclaw": ">=2026.2.0",
50
+ "viem": ">=2.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "mppx": { "optional": true },
54
+ "viem": { "optional": true }
49
55
  },
50
56
  "devDependencies": {
51
57
  "@eslint/js": "^10.0.1",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tweetclaw
3
- description: "OpenClaw plugin for X/Twitter automation. Post tweets, reply, like, retweet, follow, DM, search, extract data, run giveaways, monitor accounts via Xquik. 40+ endpoints, 2 tools (explore + tweetclaw), 2 commands (/xstatus, /xtrends), background event poller."
3
+ description: "OpenClaw plugin for X/Twitter automation. Post tweets, reply, like, retweet, follow, DM, search, extract data, run giveaways, monitor accounts, automate flows via Xquik. 97 endpoints, 2 tools (explore + tweetclaw), 2 commands (/xstatus, /xtrends), background event poller."
4
4
  homepage: https://xquik.com
5
5
  read_when:
6
6
  - Posting, replying, liking, retweeting, or following on X/Twitter
@@ -11,7 +11,7 @@ read_when:
11
11
  - Extracting bulk data from X/Twitter (followers, replies, communities)
12
12
  - Downloading tweet media or uploading images
13
13
  - Sending DMs or updating X/Twitter profile
14
- metadata: {"openclaw":{"emoji":"🐦","primaryEnv":"XQUIK_API_KEY","requires":{"env":["XQUIK_API_KEY"]}}}
14
+ metadata: {"openclaw":{"emoji":"🐦","primaryEnv":"XQUIK_API_KEY","requires":{"env":["XQUIK_API_KEY"]},"tags":["twitter","x","automation","social-media","tweets","scraping","giveaway","monitoring","rest-api"]}}
15
15
  ---
16
16
 
17
17
  # TweetClaw
@@ -22,17 +22,52 @@ OpenClaw plugin for X/Twitter automation powered by Xquik. Install via:
22
22
  openclaw plugins install @xquik/tweetclaw
23
23
  ```
24
24
 
25
+ ## When to Use
26
+
27
+ Use TweetClaw when the user wants to:
28
+
29
+ - Post tweets, reply to tweets, or delete tweets
30
+ - Like, retweet, or follow/unfollow users
31
+ - Send DMs on X/Twitter
32
+ - Update their X profile, avatar, or banner
33
+ - Upload media and tweet with images
34
+ - Search tweets or look up user profiles
35
+ - Extract bulk data (followers, replies, communities, spaces)
36
+ - Run giveaway draws from tweet replies
37
+ - Monitor X accounts for new activity
38
+ - Compose algorithm-optimized tweets
39
+ - Analyze a user's writing style
40
+ - Check trending topics on X
41
+ - Download tweet media (images, videos, GIFs)
42
+ - Set up Telegram alerts for monitor events
43
+ - Create and manage automation flows (triggers, steps, test runs)
44
+ - Open and manage support tickets
45
+ - Read X Articles (long-form posts)
46
+
47
+ Do NOT use TweetClaw for browsing X in a browser, analytics dashboards, scheduling future posts, or managing X ads.
48
+
25
49
  ## Configuration
26
50
 
27
- Set your Xquik API key (get one at [xquik.com/account-manager](https://xquik.com/account-manager)):
51
+ ### API key mode (full access)
28
52
 
29
53
  ```bash
30
54
  openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'
31
55
  ```
32
56
 
57
+ Get a key at [dashboard.xquik.com/en/account?tab=x-accounts](https://dashboard.xquik.com/en/account?tab=x-accounts).
58
+
59
+ ### MPP mode (no account, pay-per-use via Tempo/USDC)
60
+
61
+ ```bash
62
+ npm i mppx viem
63
+ openclaw config set plugins.entries.tweetclaw.config.tempoPrivateKey '0xYOUR_KEY'
64
+ ```
65
+
66
+ MPP gives agents access to 7 read-only X-API endpoints without any account or subscription. The mppx SDK handles HTTP 402 payment challenges automatically.
67
+
33
68
  ## Tools
34
69
 
35
- TweetClaw registers 2 tools that cover the entire Xquik API (40+ endpoints):
70
+ TweetClaw registers 2 tools that cover the entire Xquik API (97 endpoints):
36
71
 
37
72
  ### `explore` (free, no network)
38
73
 
@@ -177,6 +212,27 @@ You: "What's trending on X right now?"
177
212
  Agent uses tweetclaw -> returns curated trending topics from 7 sources
178
213
  ```
179
214
 
215
+ ### Create an automation flow (free)
216
+
217
+ ```
218
+ You: "Create an automation that sends a DM when I get a new follower"
219
+ Agent uses tweetclaw -> creates flow with monitor_event trigger, adds send_dm step, tests it
220
+ ```
221
+
222
+ ### Read an X Article
223
+
224
+ ```
225
+ You: "Get the full article from this tweet: https://x.com/user/status/123"
226
+ Agent uses tweetclaw -> calls /api/v1/x/articles/:tweetId, returns title, body, images
227
+ ```
228
+
229
+ ### Open a support ticket (free)
230
+
231
+ ```
232
+ You: "Open a support ticket about my monitor not working"
233
+ Agent uses tweetclaw -> creates ticket with subject and description
234
+ ```
235
+
180
236
  ## API Categories
181
237
 
182
238
  | Category | Examples | Free |
@@ -189,20 +245,28 @@ Agent uses tweetclaw -> returns curated trending topics from 7 sources
189
245
  | Extraction | Reply/follower/community extraction (20 tools) | No |
190
246
  | Draws | Giveaway draws, export results | No |
191
247
  | Monitoring | Create monitors, view events, webhooks | No |
248
+ | Automations | Create flows, add steps, test runs, inbound webhooks | Yes |
192
249
  | Account | API keys, subscription, connected X accounts | Yes |
193
250
  | Trends | X trending topics, curated radar from 7 sources | Mixed |
251
+ | Support | Create tickets, reply, track status | Yes |
194
252
 
195
253
  ## Pricing
196
254
 
197
- Free tier (no subscription): tweet composition, style analysis, drafts, curated radar, account management, integrations.
255
+ MPP pay-per-use (no account): 7 read-only X-API endpoints via Tempo (USDC) - tweet lookup ($0.0003), tweet search ($0.0003/tweet), user lookup ($0.00036), follower check ($0.002), article ($0.002), media download ($0.0003/media), trends ($0.0009).
256
+
257
+ Free tier (API key, no subscription): tweet composition, style analysis, drafts, curated radar, account management, integrations, automations (create/test), support tickets.
198
258
 
199
- Subscription ($20/month): write actions, search, media, extractions, draws, monitors, X trending.
259
+ Subscription ($20/month): write actions, search, article lookup, media, extractions, draws, monitors, X trending, flow activation (2 free, 10 subscriber).
200
260
 
201
261
  When a paid endpoint returns 402, TweetClaw provides a checkout URL.
202
262
 
203
- ## When NOT to Use
263
+ ## Tips
204
264
 
205
- - Reading tweets in a browser or basic browsing (use a browser skill instead)
206
- - X/Twitter analytics dashboards (TweetClaw returns raw data, not visualizations)
207
- - Scheduling tweets for future posting (Xquik posts immediately)
208
- - Managing X/Twitter ads or promoted content (not supported)
265
+ - Use `explore` first to discover endpoints before calling `tweetclaw` - saves tokens and avoids guessing
266
+ - Free endpoints (compose, styles, radar, drafts) work without a subscription - always try them first
267
+ - Never combine free and paid API calls in the same `Promise.all` - a 402 on one call kills all results
268
+ - For write actions (post, like, follow, DM), always pass the `account` parameter with the X username
269
+ - Follow/unfollow/DM require a numeric user ID - look up the user first via `/api/v1/x/users/:username`
270
+ - On 402 errors, call `POST /api/v1/subscribe` to get a checkout URL instead of giving up
271
+ - Use `/xstatus` to quickly check subscription and usage without invoking the AI agent
272
+ - The compose workflow (compose/refine/score) is free and helps draft high-engagement tweets
package/src/api-spec.ts CHANGED
@@ -21,13 +21,16 @@ const EXTRACTION_SEARCH_PARAMS: readonly EndpointParameter[] = [
21
21
  { description: 'Language code filter, e.g. en, tr (tweet_search_extractor)', in: 'body', name: 'language', required: false, type: 'string' },
22
22
  { description: 'Start date YYYY-MM-DD (tweet_search_extractor)', in: 'body', name: 'sinceDate', required: false, type: 'string' },
23
23
  { description: 'End date YYYY-MM-DD (tweet_search_extractor)', in: 'body', name: 'untilDate', required: false, type: 'string' },
24
- { description: 'Filter by media type: images, videos, links (tweet_search_extractor)', in: 'body', name: 'mediaType', required: false, type: 'string' },
24
+ { description: 'Filter by media type: images, videos, gifs, media (tweet_search_extractor)', in: 'body', name: 'mediaType', required: false, type: 'string' },
25
25
  { description: 'Minimum likes threshold (tweet_search_extractor)', in: 'body', name: 'minFaves', required: false, type: 'number' },
26
26
  { description: 'Minimum retweets threshold (tweet_search_extractor)', in: 'body', name: 'minRetweets', required: false, type: 'number' },
27
27
  { description: 'Minimum replies threshold (tweet_search_extractor)', in: 'body', name: 'minReplies', required: false, type: 'number' },
28
28
  { description: 'Only verified authors (tweet_search_extractor)', in: 'body', name: 'verifiedOnly', required: false, type: 'boolean' },
29
- { description: 'Include or exclude replies (tweet_search_extractor): include, exclude', in: 'body', name: 'replies', required: false, type: 'string' },
30
- { description: 'Include or exclude retweets (tweet_search_extractor): include, exclude', in: 'body', name: 'retweets', required: false, type: 'string' },
29
+ { description: 'Control reply inclusion (tweet_search_extractor): include, exclude, only', in: 'body', name: 'replies', required: false, type: 'string' },
30
+ { description: 'Control retweet inclusion (tweet_search_extractor): include, exclude, only', in: 'body', name: 'retweets', required: false, type: 'string' },
31
+ { description: 'Exact phrase match (tweet_search_extractor)', in: 'body', name: 'exactPhrase', required: false, type: 'string' },
32
+ { description: 'Comma-separated words to exclude (tweet_search_extractor)', in: 'body', name: 'excludeWords', required: false, type: 'string' },
33
+ { description: 'Raw X search operators (tweet_search_extractor)', in: 'body', name: 'advancedQuery', required: false, type: 'string' },
31
34
  ];
32
35
 
33
36
  const PARAM_STYLE_USERNAME: EndpointParameter =
@@ -84,8 +87,16 @@ const PARAM_MEDIA_URL: EndpointParameter =
84
87
  { description: 'URL to download media from (alternative to file, HTTPS only)', in: 'body', name: 'url', required: false, type: 'string' };
85
88
 
86
89
  const RESPONSE_COMMUNITY_ACTION = '{ communityId, communityName, success: true }';
90
+ const CATEGORY_AUTOMATIONS = 'automations';
91
+ const CATEGORY_SUPPORT = 'support';
87
92
  const CATEGORY_X_WRITE = 'x-write';
88
93
 
94
+ const PARAM_AUTOMATION_SLUG: EndpointParameter =
95
+ { description: 'Flow slug', in: 'path', name: 'slug', required: true, type: 'string' };
96
+
97
+ const PARAM_TICKET_ID: EndpointParameter =
98
+ { description: 'Ticket public ID', in: 'path', name: 'id', required: true, type: 'string' };
99
+
89
100
  const API_SPEC: readonly EndpointInfo[] = [
90
101
  // --- Account ---
91
102
  {
@@ -305,13 +316,14 @@ const API_SPEC: readonly EndpointInfo[] = [
305
316
  method: 'GET',
306
317
  parameters: [
307
318
  { description: 'Filter by category (general, tech, dev, etc.)', in: 'query', name: 'category', required: false, type: 'string' },
308
- { description: 'Number of items to return', in: 'query', name: 'count', required: false, type: 'number' },
309
- { description: 'Lookback window in hours', in: 'query', name: 'hours', required: false, type: 'number' },
310
- { description: 'Region filter (us, global, etc.)', in: 'query', name: 'region', required: false, type: 'string' },
311
- { description: 'Source filter (google, hackernews, reddit, etc.)', in: 'query', name: 'source', required: false, type: 'string' },
319
+ { description: 'Max items to return (1-100, default 50)', in: 'query', name: 'limit', required: false, type: 'number' },
320
+ { description: 'Lookback window in hours (1-72, default 6)', in: 'query', name: 'hours', required: false, type: 'number' },
321
+ { description: 'Region filter (US, GB, TR, ES, DE, FR, JP, IN, BR, CA, MX, global)', in: 'query', name: 'region', required: false, type: 'string' },
322
+ { description: 'Source filter (github, google_trends, hacker_news, polymarket, reddit, trustmrr, wikipedia)', in: 'query', name: 'source', required: false, type: 'string' },
323
+ { description: DESCRIPTION_PAGINATION_CURSOR, in: 'query', name: 'after', required: false, type: 'string' },
312
324
  ],
313
325
  path: '/api/v1/radar',
314
- responseShape: '{ items: [{ title, url?, score, category, source, region, publishedAt }], total }',
326
+ responseShape: '{ items: [{ title, url?, score, category, source, region, publishedAt }], hasMore, nextCursor? }',
315
327
  summary: 'Get trending topics from curated radar sources',
316
328
  },
317
329
 
@@ -374,10 +386,13 @@ const API_SPEC: readonly EndpointInfo[] = [
374
386
  free: false,
375
387
  method: 'POST',
376
388
  parameters: [
377
- { description: 'Extraction tool type (reply-extractor, community-explorer, etc.)', in: 'body', name: 'toolType', required: true, type: 'string' },
389
+ { description: 'Extraction tool type (reply_extractor, community_extractor, etc.)', in: 'body', name: 'toolType', required: true, type: 'string' },
378
390
  { description: 'Target X username', in: 'body', name: 'targetUsername', required: false, type: 'string' },
379
391
  { description: 'Target tweet ID', in: 'body', name: 'targetTweetId', required: false, type: 'string' },
380
- { description: 'Search query for tweet search tools', in: 'body', name: 'searchQuery', required: false, type: 'string' },
392
+ { description: 'Search query for search tools', in: 'body', name: 'searchQuery', required: false, type: 'string' },
393
+ { description: 'Community ID for community tools', in: 'body', name: 'targetCommunityId', required: false, type: 'string' },
394
+ { description: 'List ID for list tools', in: 'body', name: 'targetListId', required: false, type: 'string' },
395
+ { description: 'Space ID for space_explorer', in: 'body', name: 'targetSpaceId', required: false, type: 'string' },
381
396
  { description: 'Max results to return', in: 'body', name: 'resultsLimit', required: false, type: 'number' },
382
397
  ...EXTRACTION_SEARCH_PARAMS,
383
398
  ],
@@ -393,6 +408,10 @@ const API_SPEC: readonly EndpointInfo[] = [
393
408
  { description: 'Extraction tool type', in: 'body', name: 'toolType', required: true, type: 'string' },
394
409
  { description: 'Target X username', in: 'body', name: 'targetUsername', required: false, type: 'string' },
395
410
  { description: 'Target tweet ID', in: 'body', name: 'targetTweetId', required: false, type: 'string' },
411
+ { description: 'Search query for search tools', in: 'body', name: 'searchQuery', required: false, type: 'string' },
412
+ { description: 'Community ID for community tools', in: 'body', name: 'targetCommunityId', required: false, type: 'string' },
413
+ { description: 'List ID for list tools', in: 'body', name: 'targetListId', required: false, type: 'string' },
414
+ { description: 'Space ID for space_explorer', in: 'body', name: 'targetSpaceId', required: false, type: 'string' },
396
415
  { description: 'Max results to return', in: 'body', name: 'resultsLimit', required: false, type: 'number' },
397
416
  ...EXTRACTION_SEARCH_PARAMS,
398
417
  ],
@@ -569,6 +588,7 @@ const API_SPEC: readonly EndpointInfo[] = [
569
588
  parameters: [
570
589
  { description: 'Tweet ID to look up', in: 'path', name: 'tweetId', required: true, type: 'string' },
571
590
  ],
591
+ mpp: { intent: 'charge', price: '$0.0003/call' },
572
592
  path: '/api/v1/x/tweets/:tweetId',
573
593
  responseShape: '{ tweet: { id, text, likeCount, retweetCount, replyCount, viewCount, ... }, author? }',
574
594
  summary: 'Look up a single tweet with engagement metrics',
@@ -581,6 +601,7 @@ const API_SPEC: readonly EndpointInfo[] = [
581
601
  { description: 'Search query (X search syntax)', in: 'query', name: 'q', required: true, type: 'string' },
582
602
  { description: 'Max tweets to return (default 20, max 200)', in: 'query', name: 'limit', required: false, type: 'number' },
583
603
  ],
604
+ mpp: { intent: 'session', price: '$0.0003/tweet' },
584
605
  path: '/api/v1/x/tweets/search',
585
606
  responseShape: '{ tweets: [{ id, text, author?, likeCount?, retweetCount?, media? }], total }',
586
607
  summary: 'Search tweets by query with optional limit for pagination',
@@ -592,6 +613,7 @@ const API_SPEC: readonly EndpointInfo[] = [
592
613
  parameters: [
593
614
  { description: 'X username to look up', in: 'path', name: 'username', required: true, type: 'string' },
594
615
  ],
616
+ mpp: { intent: 'charge', price: '$0.00036/call' },
595
617
  path: '/api/v1/x/users/:username',
596
618
  responseShape: '{ id, username, name, followers?, following?, verified?, description? }',
597
619
  summary: 'Get X user profile by username',
@@ -604,10 +626,23 @@ const API_SPEC: readonly EndpointInfo[] = [
604
626
  { description: 'Source username', in: 'query', name: 'source', required: true, type: 'string' },
605
627
  { description: 'Target username', in: 'query', name: 'target', required: true, type: 'string' },
606
628
  ],
629
+ mpp: { intent: 'charge', price: '$0.002/call' },
607
630
  path: '/api/v1/x/followers/check',
608
631
  responseShape: '{ isFollowing, isFollowedBy, sourceUsername, targetUsername }',
609
632
  summary: 'Check follow relationship between two users',
610
633
  },
634
+ {
635
+ category: 'twitter',
636
+ free: false,
637
+ method: 'GET',
638
+ parameters: [
639
+ { description: 'Tweet ID of the X Article', in: 'path', name: 'tweetId', required: true, type: 'string' },
640
+ ],
641
+ mpp: { intent: 'charge', price: '$0.002/call' },
642
+ path: '/api/v1/x/articles/:tweetId',
643
+ responseShape: '{ article: { title, previewText, coverImageUrl, contents, createdAt, likeCount, replyCount, quoteCount, viewCount }, author? }',
644
+ summary: 'Get full content of an X Article (long-form post) by tweet ID',
645
+ },
611
646
 
612
647
  // --- Media ---
613
648
  {
@@ -618,6 +653,7 @@ const API_SPEC: readonly EndpointInfo[] = [
618
653
  { description: 'Tweet URL or ID (single tweet)', in: 'body', name: 'tweetInput', required: false, type: 'string' },
619
654
  { description: 'Array of tweet URLs or IDs (bulk, max 50)', in: 'body', name: 'tweetIds', required: false, type: 'string[]' },
620
655
  ],
656
+ mpp: { intent: 'session', price: '$0.0003/media' },
621
657
  path: '/api/v1/x/media/download',
622
658
  responseShape: 'Single: { tweetId, galleryUrl, cacheHit }. Bulk: { galleryUrl, totalTweets, totalMedia }',
623
659
  summary: 'Download media from tweets. Single tweetInput or bulk tweetIds. Returns gallery URL.',
@@ -632,6 +668,7 @@ const API_SPEC: readonly EndpointInfo[] = [
632
668
  { description: 'WOEID location ID (1 for worldwide)', in: 'query', name: 'woeid', required: false, type: 'number' },
633
669
  { description: 'Max number of trends', in: 'query', name: 'count', required: false, type: 'number' },
634
670
  ],
671
+ mpp: { intent: 'charge', price: '$0.0009/call' },
635
672
  path: '/api/v1/trends',
636
673
  responseShape: '{ trends: [{ name, query?, description?, rank? }], total, woeid }',
637
674
  summary: 'Get current trending topics on X',
@@ -1019,6 +1056,201 @@ const API_SPEC: readonly EndpointInfo[] = [
1019
1056
  responseShape: RESPONSE_COMMUNITY_ACTION,
1020
1057
  summary: 'Leave community',
1021
1058
  },
1059
+
1060
+ // --- Automations ---
1061
+ {
1062
+ category: CATEGORY_AUTOMATIONS,
1063
+ free: true,
1064
+ method: 'GET',
1065
+ path: '/api/v1/automations',
1066
+ responseShape: '{ items: [{ id, name, slug, triggerType, triggerConfig, isActive, runCount, lastRunAt, createdAt, updatedAt }] }',
1067
+ summary: 'List all automation flows',
1068
+ },
1069
+ {
1070
+ category: CATEGORY_AUTOMATIONS,
1071
+ free: true,
1072
+ method: 'POST',
1073
+ parameters: [
1074
+ { description: 'Flow name', in: 'body', name: 'name', required: true, type: 'string' },
1075
+ { description: 'Trigger type: monitor_event, schedule, search, webhook_inbound', in: 'body', name: 'triggerType', required: true, type: 'string' },
1076
+ { description: 'Trigger-specific configuration', in: 'body', name: 'triggerConfig', required: true, type: 'object' },
1077
+ { description: 'Template slug to scaffold from', in: 'body', name: 'templateSlug', required: false, type: 'string' },
1078
+ ],
1079
+ path: '/api/v1/automations',
1080
+ responseShape: '{ id, name, slug, triggerType, triggerConfig, isActive, createdAt, updatedAt }',
1081
+ summary: 'Create a new automation flow',
1082
+ },
1083
+ {
1084
+ category: CATEGORY_AUTOMATIONS,
1085
+ free: true,
1086
+ method: 'GET',
1087
+ parameters: [PARAM_AUTOMATION_SLUG],
1088
+ path: '/api/v1/automations/:slug',
1089
+ responseShape: '{ id, name, slug, triggerType, triggerConfig, isActive, steps, recentRuns, createdAt, updatedAt }',
1090
+ summary: 'Get flow details with steps and recent runs',
1091
+ },
1092
+ {
1093
+ category: CATEGORY_AUTOMATIONS,
1094
+ free: true,
1095
+ method: 'PATCH',
1096
+ parameters: [
1097
+ PARAM_AUTOMATION_SLUG,
1098
+ { description: 'Current updatedAt for optimistic concurrency', in: 'body', name: 'expectedUpdatedAt', required: true, type: 'string' },
1099
+ { description: 'Updated flow name', in: 'body', name: 'name', required: false, type: 'string' },
1100
+ { description: 'Updated trigger type', in: 'body', name: 'triggerType', required: false, type: 'string' },
1101
+ { description: 'Updated trigger config', in: 'body', name: 'triggerConfig', required: false, type: 'object' },
1102
+ { description: 'Activate or deactivate', in: 'body', name: 'isActive', required: false, type: 'boolean' },
1103
+ ],
1104
+ path: '/api/v1/automations/:slug',
1105
+ responseShape: '{ id, name, slug, triggerType, triggerConfig, isActive, createdAt, updatedAt }',
1106
+ summary: 'Update flow name, trigger, or active status',
1107
+ },
1108
+ {
1109
+ category: CATEGORY_AUTOMATIONS,
1110
+ free: true,
1111
+ method: 'DELETE',
1112
+ parameters: [PARAM_AUTOMATION_SLUG],
1113
+ path: '/api/v1/automations/:slug',
1114
+ responseShape: RESPONSE_SUCCESS,
1115
+ summary: 'Delete a flow and all its steps',
1116
+ },
1117
+ {
1118
+ category: CATEGORY_AUTOMATIONS,
1119
+ free: true,
1120
+ method: 'POST',
1121
+ parameters: [
1122
+ PARAM_AUTOMATION_SLUG,
1123
+ { description: 'Step type: action, condition, extraction', in: 'body', name: 'stepType', required: true, type: 'string' },
1124
+ { description: 'Branch: main, if_true, if_false', in: 'body', name: 'branch', required: true, type: 'string' },
1125
+ { description: 'Step-specific configuration', in: 'body', name: 'config', required: true, type: 'object' },
1126
+ { description: 'Order position in branch', in: 'body', name: 'position', required: false, type: 'number' },
1127
+ { description: 'Parent step ID for condition branches', in: 'body', name: 'parentStepId', required: false, type: 'string' },
1128
+ { description: 'Action type: create_tweet, follow, like, reply_tweet, retweet, send_dm, send_email, send_telegram, unfollow', in: 'body', name: 'actionType', required: false, type: 'string' },
1129
+ { description: 'Extraction tool type', in: 'body', name: 'extractionType', required: false, type: 'string' },
1130
+ { description: 'Variable name for extraction output', in: 'body', name: 'outputName', required: false, type: 'string' },
1131
+ ],
1132
+ path: '/api/v1/automations/:slug/steps',
1133
+ responseShape: '{ id, flowId, stepType, actionType, extractionType, branch, config, position, createdAt }',
1134
+ summary: 'Add an action, condition, or extraction step to a flow',
1135
+ },
1136
+ {
1137
+ category: CATEGORY_AUTOMATIONS,
1138
+ free: true,
1139
+ method: 'PATCH',
1140
+ parameters: [
1141
+ PARAM_AUTOMATION_SLUG,
1142
+ { description: 'Step ID to update', in: 'body', name: 'stepId', required: true, type: 'string' },
1143
+ { description: 'Updated step config', in: 'body', name: 'config', required: false, type: 'object' },
1144
+ { description: 'Updated step type', in: 'body', name: 'stepType', required: false, type: 'string' },
1145
+ { description: 'Updated branch', in: 'body', name: 'branch', required: false, type: 'string' },
1146
+ { description: 'Updated position', in: 'body', name: 'position', required: false, type: 'number' },
1147
+ { description: 'Updated action type', in: 'body', name: 'actionType', required: false, type: 'string' },
1148
+ { description: 'Updated extraction type', in: 'body', name: 'extractionType', required: false, type: 'string' },
1149
+ { description: 'Updated output variable name', in: 'body', name: 'outputName', required: false, type: 'string' },
1150
+ ],
1151
+ path: '/api/v1/automations/:slug/steps',
1152
+ responseShape: '{ id, flowId, stepType, actionType, extractionType, branch, config, position, createdAt }',
1153
+ summary: 'Update a step configuration or position',
1154
+ },
1155
+ {
1156
+ category: CATEGORY_AUTOMATIONS,
1157
+ free: true,
1158
+ method: 'DELETE',
1159
+ parameters: [
1160
+ PARAM_AUTOMATION_SLUG,
1161
+ { description: 'Step ID to delete', in: 'body', name: 'stepId', required: true, type: 'string' },
1162
+ ],
1163
+ path: '/api/v1/automations/:slug/steps',
1164
+ responseShape: RESPONSE_SUCCESS,
1165
+ summary: 'Remove a step from a flow',
1166
+ },
1167
+ {
1168
+ category: CATEGORY_AUTOMATIONS,
1169
+ free: true,
1170
+ method: 'PATCH',
1171
+ parameters: [
1172
+ PARAM_AUTOMATION_SLUG,
1173
+ { description: 'Array of { stepId, positionX, positionY } (max 10)', in: 'body', name: 'positions', required: true, type: 'array' },
1174
+ ],
1175
+ path: '/api/v1/automations/:slug/steps/positions',
1176
+ responseShape: RESPONSE_SUCCESS,
1177
+ summary: 'Batch update canvas positions for flow steps',
1178
+ },
1179
+ {
1180
+ category: CATEGORY_AUTOMATIONS,
1181
+ free: true,
1182
+ method: 'POST',
1183
+ parameters: [PARAM_AUTOMATION_SLUG],
1184
+ path: '/api/v1/automations/:slug/test',
1185
+ responseShape: '{ status, result, runId, error? }',
1186
+ summary: 'Test run a flow with synthetic trigger data',
1187
+ },
1188
+ {
1189
+ category: CATEGORY_AUTOMATIONS,
1190
+ free: true,
1191
+ method: 'POST',
1192
+ parameters: [
1193
+ { description: 'Inbound webhook token', in: 'path', name: 'token', required: true, type: 'string' },
1194
+ ],
1195
+ path: '/api/v1/webhooks/inbound/:token',
1196
+ responseShape: '{ accepted: true, flowId }',
1197
+ summary: 'Trigger a flow via inbound webhook (no auth required, token acts as auth)',
1198
+ },
1199
+
1200
+ // --- Support ---
1201
+ {
1202
+ category: CATEGORY_SUPPORT,
1203
+ free: true,
1204
+ method: 'POST',
1205
+ parameters: [
1206
+ { description: 'Ticket subject (1-500 chars)', in: 'body', name: 'subject', required: true, type: 'string' },
1207
+ { description: 'Initial message (1-10000 chars)', in: 'body', name: 'body', required: true, type: 'string' },
1208
+ ],
1209
+ path: '/api/v1/support/tickets',
1210
+ responseShape: '{ publicId }',
1211
+ summary: 'Open a new support ticket',
1212
+ },
1213
+ {
1214
+ category: CATEGORY_SUPPORT,
1215
+ free: true,
1216
+ method: 'GET',
1217
+ path: '/api/v1/support/tickets',
1218
+ responseShape: '{ tickets: [{ publicId, subject, status, messageCount, createdAt, updatedAt }] }',
1219
+ summary: 'List your support tickets',
1220
+ },
1221
+ {
1222
+ category: CATEGORY_SUPPORT,
1223
+ free: true,
1224
+ method: 'GET',
1225
+ parameters: [PARAM_TICKET_ID],
1226
+ path: '/api/v1/support/tickets/:id',
1227
+ responseShape: '{ publicId, subject, status, messages: [{ body, sender, createdAt }], createdAt, updatedAt }',
1228
+ summary: 'Get a ticket with message history',
1229
+ },
1230
+ {
1231
+ category: CATEGORY_SUPPORT,
1232
+ free: true,
1233
+ method: 'PATCH',
1234
+ parameters: [
1235
+ PARAM_TICKET_ID,
1236
+ { description: 'New status: open, resolved, closed', in: 'body', name: 'status', required: true, type: 'string' },
1237
+ ],
1238
+ path: '/api/v1/support/tickets/:id',
1239
+ responseShape: '{ publicId, status }',
1240
+ summary: 'Update ticket status',
1241
+ },
1242
+ {
1243
+ category: CATEGORY_SUPPORT,
1244
+ free: true,
1245
+ method: 'POST',
1246
+ parameters: [
1247
+ PARAM_TICKET_ID,
1248
+ { description: 'Message content (1-10000 chars)', in: 'body', name: 'body', required: true, type: 'string' },
1249
+ ],
1250
+ path: '/api/v1/support/tickets/:id/messages',
1251
+ responseShape: '{ publicId }',
1252
+ summary: 'Reply to a support ticket',
1253
+ },
1022
1254
  ] as const;
1023
1255
 
1024
1256
  export { API_SPEC };
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { handleXStatus } from './commands/xstatus.js';
2
2
  import { handleXTrends } from './commands/xtrends.js';
3
+ import { initMpp } from './mpp.js';
3
4
  import { createProxiedRequest } from './request.js';
4
5
  import { createEventPoller } from './services/event-poller.js';
5
6
  import { handleExplore, SEARCH_DESCRIPTION } from './tools/explore.js';
@@ -16,7 +17,8 @@ function isPollerEvent(value: unknown): value is PollerEvent {
16
17
  }
17
18
 
18
19
  function isPluginConfig(value: unknown): value is PluginConfig {
19
- return typeof value === 'object' && value !== null && 'apiKey' in value;
20
+ if (typeof value !== 'object' || value === null) return false;
21
+ return 'apiKey' in value || 'tempoPrivateKey' in value;
20
22
  }
21
23
 
22
24
  const DEFAULT_POLLING_INTERVAL_SECONDS = 60;
@@ -74,13 +76,28 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
74
76
  const config: unknown = api.pluginConfig;
75
77
  if (!isPluginConfig(config)) {
76
78
  api.logger.warn(
77
- "TweetClaw: No API key configured. Run: openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY'",
79
+ "TweetClaw: No API key or Tempo wallet configured. Run: openclaw config set plugins.entries.tweetclaw.config.apiKey 'xq_YOUR_KEY' or set tempoPrivateKey for MPP pay-per-use",
78
80
  );
79
81
  return;
80
82
  }
81
83
 
82
- const { apiKey, baseUrl = 'https://xquik.com' } = config;
83
- const request = createProxiedRequest(baseUrl, apiKey, fetchFunction);
84
+ const { apiKey, baseUrl = 'https://xquik.com', tempoPrivateKey } = config;
85
+ const isMppMode = apiKey === undefined && tempoPrivateKey !== undefined;
86
+ const credential = apiKey ?? '';
87
+
88
+ if (isMppMode) {
89
+ void (async (): Promise<void> => {
90
+ try {
91
+ await initMpp(tempoPrivateKey);
92
+ api.logger.info('TweetClaw: MPP initialized - Tempo wallet ready');
93
+ } catch (error: unknown) {
94
+ api.logger.error(`TweetClaw: MPP init failed - ${error instanceof Error ? error.message : String(error)}`);
95
+ }
96
+ })();
97
+ api.logger.info('TweetClaw: MPP mode - pay-per-use via Tempo (7 X-API endpoints, no subscription needed)');
98
+ }
99
+
100
+ const request = createProxiedRequest(baseUrl, credential, fetchFunction);
84
101
 
85
102
  // --- Tools (2-tool approach, execute inside tool object) ---
86
103
  api.registerTool(
@@ -96,7 +113,7 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
96
113
  api.registerTool(
97
114
  {
98
115
  description: EXECUTE_DESCRIPTION,
99
- execute: async (_toolCallId, { code }) => handleTweetclaw({ apiKey, baseUrl, code, fetchFunction }),
116
+ execute: async (_toolCallId, { code }) => handleTweetclaw({ apiKey: credential, baseUrl, code, fetchFunction }),
100
117
  name: 'tweetclaw',
101
118
  parameters: CODE_PARAMETER,
102
119
  },
@@ -104,14 +121,16 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
104
121
  );
105
122
 
106
123
  // --- Commands (instant, no LLM) ---
107
- api.registerCommand({
108
- description: 'Show Xquik account status & usage',
109
- handler: async () => {
110
- const text = await handleXStatus(request);
111
- return { text };
112
- },
113
- name: 'xstatus',
114
- });
124
+ if (!isMppMode) {
125
+ api.registerCommand({
126
+ description: 'Show Xquik account status & usage',
127
+ handler: async () => {
128
+ const text = await handleXStatus(request);
129
+ return { text };
130
+ },
131
+ name: 'xstatus',
132
+ });
133
+ }
115
134
 
116
135
  api.registerCommand({
117
136
  acceptsArgs: true,
@@ -123,9 +142,9 @@ export default function register(api: OpenClawApi, fetchFunction?: FetchFunction
123
142
  name: 'xtrends',
124
143
  });
125
144
 
126
- // --- Background event poller ---
145
+ // --- Background event poller (requires API key, not available in MPP mode) ---
127
146
  const { pollingEnabled, pollingInterval } = config;
128
- if (pollingEnabled !== false) {
147
+ if (!isMppMode && pollingEnabled !== false) {
129
148
  const poller = createEventPoller({
130
149
  intervalSeconds: pollingInterval ?? DEFAULT_POLLING_INTERVAL_SECONDS,
131
150
  onEvents: (events) => {
package/src/mpp.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { resolveAsyncFunctionConstructor } from './tools/sandbox.js';
2
+
3
+ type ModuleLoader = (name: string) => Promise<Record<string, unknown>>;
4
+
5
+ function isRecord(value: unknown): value is Record<string, unknown> {
6
+ return typeof value === 'object' && value !== null;
7
+ }
8
+
9
+ function isCallable(value: unknown): value is (...args: readonly unknown[]) => unknown {
10
+ return typeof value === 'function';
11
+ }
12
+
13
+ function createModuleLoader(): ModuleLoader {
14
+ const loader = resolveAsyncFunctionConstructor();
15
+ const dynamicImport = new loader('n', 'return import(n)');
16
+ return async (name: string): Promise<Record<string, unknown>> => {
17
+ const mod: unknown = await dynamicImport(name);
18
+ if (!isRecord(mod)) {
19
+ throw new Error(`Failed to load ${name}`);
20
+ }
21
+ return mod;
22
+ };
23
+ }
24
+
25
+ async function initMpp(tempoPrivateKey: string, loadModule?: ModuleLoader): Promise<void> {
26
+ const load = loadModule ?? createModuleLoader();
27
+ const mppxMod = await load('mppx/client').catch((): never => {
28
+ throw new Error('MPP requires mppx package. Run: npm i mppx viem');
29
+ });
30
+ const viemMod = await load('viem/accounts').catch((): never => {
31
+ throw new Error('MPP requires viem package. Run: npm i mppx viem');
32
+ });
33
+ if (!isCallable(viemMod.privateKeyToAccount)) throw new Error('viem missing privateKeyToAccount');
34
+ if (!isCallable(mppxMod.tempo)) throw new Error('mppx missing tempo');
35
+ if (!isRecord(mppxMod.Mppx)) throw new Error('mppx missing Mppx');
36
+ const createMethod: unknown = mppxMod.Mppx.create;
37
+ if (!isCallable(createMethod)) throw new Error('mppx Mppx.create is not a function');
38
+ const account: unknown = viemMod.privateKeyToAccount(tempoPrivateKey);
39
+ const method: unknown = mppxMod.tempo({ account });
40
+ createMethod({ methods: [method] });
41
+ }
42
+
43
+ export { createModuleLoader, initMpp, isCallable, isRecord };
44
+ export type { ModuleLoader };
package/src/request.ts CHANGED
@@ -16,7 +16,7 @@ function buildAuthHeader(credential: string): Record<string, string> {
16
16
  }
17
17
 
18
18
  function buildFetchHeaders(credential: string, hasBody: boolean): Record<string, string> {
19
- const auth = buildAuthHeader(credential);
19
+ const auth = credential === '' ? {} : buildAuthHeader(credential);
20
20
  if (hasBody) {
21
21
  return { ...auth, [CONTENT_TYPE_HEADER]: 'application/json' };
22
22
  }
@@ -296,10 +296,12 @@ async () => {
296
296
  \`\`\`
297
297
 
298
298
  ## Cost
299
- - Free: /api/v1/compose, /api/v1/styles (cached lookup/save/delete/compare), /api/v1/drafts, /api/v1/radar, /api/v1/subscribe, /api/v1/account, /api/v1/api-keys, /api/v1/bot/*, /api/v1/integrations/*, /api/v1/x/accounts
300
- - Subscription required: /api/v1/styles (X API refresh when cache >7 days), /api/v1/x/tweets, /api/v1/x/users, /api/v1/x/followers, /api/v1/x/media, /api/v1/x/profile, /api/v1/x/communities, /api/v1/x/dm, /api/v1/extractions, /api/v1/draws, /api/v1/monitors, /api/v1/events, /api/v1/webhooks, /api/v1/styles/:username/performance, /api/v1/trends, /api/v1/trending/:source
299
+ - Free: /api/v1/compose, /api/v1/styles (cached lookup/save/delete/compare), /api/v1/drafts, /api/v1/radar, /api/v1/subscribe, /api/v1/account, /api/v1/api-keys, /api/v1/bot/*, /api/v1/integrations/*, /api/v1/x/accounts, /api/v1/automations/*, /api/v1/support/*
300
+ - MPP pay-per-use (no account/subscription needed, 7 endpoints): GET /api/v1/x/tweets/:id ($0.0003/call), GET /api/v1/x/tweets/search ($0.0003/tweet), GET /api/v1/x/users/:username ($0.00036/call), GET /api/v1/x/followers/check ($0.002/call), GET /api/v1/x/articles/:tweetId ($0.002/call), POST /api/v1/x/media/download ($0.0003/media), GET /api/v1/trends ($0.0009/call)
301
+ - Subscription required: /api/v1/styles (X API refresh when cache >7 days), /api/v1/x/profile, /api/v1/x/communities, /api/v1/x/dm, /api/v1/extractions, /api/v1/draws, /api/v1/monitors, /api/v1/events, /api/v1/webhooks, /api/v1/styles/:username/performance, /api/v1/trending/:source
301
302
  - Write actions (subscription required): POST /api/v1/x/tweets, DELETE /api/v1/x/tweets/:id, POST|DELETE /api/v1/x/tweets/:id/like, POST /api/v1/x/tweets/:id/retweet, POST|DELETE /api/v1/x/users/:id/follow, POST /api/v1/x/dm/:userId, POST /api/v1/x/media, PATCH /api/v1/x/profile, PATCH /api/v1/x/profile/avatar, PATCH /api/v1/x/profile/banner, POST|DELETE /api/v1/x/communities, POST|DELETE /api/v1/x/communities/:id/join
302
303
  - IMPORTANT: Always attempt the request. Never assume subscription status. The API returns a clear error if subscription is missing.
304
+ - MPP MODE: When configured with tempoPrivateKey (no API key), the mppx SDK auto-handles 402 challenges by paying via Tempo (USDC). Only the 7 MPP-eligible endpoints work in this mode.
303
305
 
304
306
  ## Error handling
305
307
  - If response contains "subscription is inactive" or status 402, call POST /api/v1/subscribe to get checkout URL
package/src/types.ts CHANGED
@@ -10,6 +10,7 @@ interface EndpointInfo {
10
10
  readonly category: string;
11
11
  readonly free: boolean;
12
12
  readonly method: string;
13
+ readonly mpp?: { readonly intent: string; readonly price: string };
13
14
  readonly parameters?: readonly EndpointParameter[];
14
15
  readonly path: string;
15
16
  readonly responseShape?: string;
@@ -32,10 +33,11 @@ interface ToolResult {
32
33
  }
33
34
 
34
35
  interface PluginConfig {
35
- readonly apiKey: string;
36
+ readonly apiKey?: string;
36
37
  readonly baseUrl?: string;
37
38
  readonly pollingEnabled?: boolean;
38
39
  readonly pollingInterval?: number;
40
+ readonly tempoPrivateKey?: string;
39
41
  }
40
42
 
41
43
  interface EventPollerOptions {