@xquik/tweetclaw 1.6.5 → 1.6.7

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.
Files changed (53) hide show
  1. package/README.md +22 -13
  2. package/dist/api-spec.d.ts +3 -0
  3. package/dist/api-spec.js +1427 -0
  4. package/dist/api-spec.js.map +1 -0
  5. package/dist/commands/xstatus.d.ts +17 -0
  6. package/dist/commands/xstatus.js +52 -0
  7. package/dist/commands/xstatus.js.map +1 -0
  8. package/dist/commands/xtrends.d.ts +16 -0
  9. package/dist/commands/xtrends.js +39 -0
  10. package/dist/commands/xtrends.js.map +1 -0
  11. package/dist/index.d.ts +107 -0
  12. package/dist/index.js +249 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/mpp.d.ts +7 -0
  15. package/dist/mpp.js +39 -0
  16. package/dist/mpp.js.map +1 -0
  17. package/dist/request.d.ts +7 -0
  18. package/dist/request.js +88 -0
  19. package/dist/request.js.map +1 -0
  20. package/dist/services/event-poller.d.ts +7 -0
  21. package/dist/services/event-poller.js +69 -0
  22. package/dist/services/event-poller.js.map +1 -0
  23. package/dist/tools/catalog.d.ts +17 -0
  24. package/dist/tools/catalog.js +110 -0
  25. package/dist/tools/catalog.js.map +1 -0
  26. package/dist/tools/explore.d.ts +5 -0
  27. package/dist/tools/explore.js +28 -0
  28. package/dist/tools/explore.js.map +1 -0
  29. package/dist/tools/result.d.ts +5 -0
  30. package/dist/tools/result.js +15 -0
  31. package/dist/tools/result.js.map +1 -0
  32. package/dist/tools/tweetclaw.d.ts +13 -0
  33. package/dist/tools/tweetclaw.js +62 -0
  34. package/dist/tools/tweetclaw.js.map +1 -0
  35. package/dist/truncate.d.ts +3 -0
  36. package/dist/truncate.js +25 -0
  37. package/dist/truncate.js.map +1 -0
  38. package/dist/types.d.ts +64 -0
  39. package/dist/types.js +2 -0
  40. package/dist/types.js.map +1 -0
  41. package/openclaw.plugin.json +7 -8
  42. package/package.json +19 -18
  43. package/skills/tweetclaw/SKILL.md +33 -42
  44. package/src/api-spec.ts +480 -12
  45. package/src/index.ts +135 -36
  46. package/src/mpp.ts +9 -11
  47. package/src/request.ts +27 -2
  48. package/src/tools/catalog.ts +145 -0
  49. package/src/tools/explore.ts +18 -44
  50. package/src/tools/result.ts +19 -0
  51. package/src/tools/tweetclaw.ts +49 -296
  52. package/src/types.ts +19 -0
  53. package/src/tools/executor.ts +0 -125
package/src/api-spec.ts CHANGED
@@ -5,6 +5,13 @@ const DESCRIPTION_PAGINATION_CURSOR = 'Pagination cursor';
5
5
  const DESCRIPTION_STYLE_USERNAME = 'X username of cached style';
6
6
  const DESCRIPTION_EXPORT_FORMAT = 'Export format (csv, json, md, md-document, pdf, txt, xlsx)';
7
7
  const CATEGORY_X_ACCOUNTS = 'x-accounts';
8
+ const MPP_PRICE_CALL = '$0.00015/call';
9
+ const MPP_PRICE_COMMUNITY = '$0.00015/community';
10
+ const MPP_PRICE_FOLLOW_CHECK = '$0.00105/call';
11
+ const MPP_PRICE_MEDIA = '$0.00015/media';
12
+ const MPP_PRICE_TREND = '$0.00045/call';
13
+ const MPP_PRICE_TWEET = '$0.00015/tweet';
14
+ const MPP_PRICE_USER = '$0.00015/user';
8
15
 
9
16
  const PAGINATION_PARAMS: readonly EndpointParameter[] = [
10
17
  { description: 'Max items per page', in: 'query', name: 'limit', required: false, type: 'number' },
@@ -83,6 +90,51 @@ const PARAM_USER_ID_REMOVE_FOLLOWER: EndpointParameter =
83
90
  const PARAM_MEDIA_URL: EndpointParameter =
84
91
  { description: 'URL to download media from (alternative to file, HTTPS only)', in: 'body', name: 'url', required: false, type: 'string' };
85
92
 
93
+ const PARAM_KEYWORD_MONITOR_ID: EndpointParameter =
94
+ { description: 'Keyword monitor ID', in: 'path', name: 'id', required: true, type: 'string' };
95
+
96
+ const PARAM_CURSOR: EndpointParameter =
97
+ { description: 'Pagination cursor from previous response', in: 'query', name: 'cursor', required: false, type: 'string' };
98
+
99
+ const PARAM_AFTER_ALIAS: EndpointParameter =
100
+ { description: 'Legacy cursor alias. Prefer cursor.', in: 'query', name: 'after', required: false, type: 'string' };
101
+
102
+ const PARAM_PAGE_SIZE_20: EndpointParameter =
103
+ { description: 'Upper bound for items per page (20-200, default 20)', in: 'query', name: 'pageSize', required: false, type: 'number' };
104
+
105
+ const PARAM_PAGE_SIZE_200: EndpointParameter =
106
+ { description: 'Upper bound for items per page (20-200, default 200)', in: 'query', name: 'pageSize', required: false, type: 'number' };
107
+
108
+ const PARAM_LIMIT_ALIAS: EndpointParameter =
109
+ { description: 'Legacy page size upper-bound alias. Prefer pageSize.', in: 'query', name: 'limit', required: false, type: 'number' };
110
+
111
+ const PARAM_QUERY_TYPE: EndpointParameter =
112
+ { description: 'Sort order: Latest or Top', in: 'query', name: 'queryType', required: false, type: 'string' };
113
+
114
+ const PARAM_SEARCH_QUERY: EndpointParameter =
115
+ { description: 'Search query', in: 'query', name: 'q', required: true, type: 'string' };
116
+
117
+ const PARAM_SINCE_TIME: EndpointParameter =
118
+ { description: 'Filter results since this Unix timestamp in seconds', in: 'query', name: 'sinceTime', required: false, type: 'number' };
119
+
120
+ const PARAM_UNTIL_TIME: EndpointParameter =
121
+ { description: 'Filter results until this Unix timestamp in seconds', in: 'query', name: 'untilTime', required: false, type: 'number' };
122
+
123
+ const PARAM_USER_ID: EndpointParameter =
124
+ { description: 'User ID or username', in: 'path', name: 'id', required: true, type: 'string' };
125
+
126
+ const PARAM_LIST_ID: EndpointParameter =
127
+ { description: 'List ID', in: 'path', name: 'id', required: true, type: 'string' };
128
+
129
+ const RESPONSE_TWEET =
130
+ '{ id, text, created?, retweet_count?, reply_count?, like_count?, quote_count?, view_count?, bookmark_count?, media?, url?, lang?, is_reply?, is_note_tweet?, is_quote_status?, in_reply_to_id?, conversation_id?, source?, entities?, quoted_tweet?, author? }';
131
+ const RESPONSE_TWEET_BASIC =
132
+ '{ id, text, created?, retweet_count?, reply_count?, like_count?, quote_count?, view_count?, bookmark_count?, media?, url?, lang?, is_reply?, in_reply_to_id?, conversation_id?, source?, entities?, author? }';
133
+ const RESPONSE_TWEETS_PAGINATED = `{ tweets: [${RESPONSE_TWEET}], has_more, next_cursor }`;
134
+ const RESPONSE_USER =
135
+ '{ id, username, name, followers?, following?, verified?, profile_picture?, cover_picture?, description?, location?, created?, statuses_count?, media_count?, can_dm? }';
136
+ const RESPONSE_USERS_PAGINATED = `{ users: [${RESPONSE_USER}], has_more, next_cursor }`;
137
+
86
138
  const RESPONSE_COMMUNITY_ACTION = '{ communityId, communityName, success: true }';
87
139
  const CATEGORY_SUPPORT = 'support';
88
140
  const CATEGORY_X_WRITE = 'x-write';
@@ -101,6 +153,7 @@ const API_SPEC: readonly EndpointInfo[] = [
101
153
  summary: 'Get current account info and subscription status',
102
154
  },
103
155
  {
156
+ agentProhibited: true,
104
157
  category: 'account',
105
158
  free: true,
106
159
  method: 'PATCH',
@@ -112,6 +165,7 @@ const API_SPEC: readonly EndpointInfo[] = [
112
165
  summary: 'Update account settings such as locale',
113
166
  },
114
167
  {
168
+ agentProhibited: true,
115
169
  category: 'account',
116
170
  free: true,
117
171
  method: 'PUT',
@@ -123,6 +177,7 @@ const API_SPEC: readonly EndpointInfo[] = [
123
177
  summary: 'Set or update linked X username',
124
178
  },
125
179
  {
180
+ agentProhibited: true,
126
181
  category: 'account',
127
182
  free: true,
128
183
  method: 'GET',
@@ -131,6 +186,7 @@ const API_SPEC: readonly EndpointInfo[] = [
131
186
  summary: 'List all API keys for the account',
132
187
  },
133
188
  {
189
+ agentProhibited: true,
134
190
  category: 'account',
135
191
  free: true,
136
192
  method: 'POST',
@@ -142,6 +198,7 @@ const API_SPEC: readonly EndpointInfo[] = [
142
198
  summary: 'Create a new API key',
143
199
  },
144
200
  {
201
+ agentProhibited: true,
145
202
  category: 'account',
146
203
  free: true,
147
204
  method: 'DELETE',
@@ -153,6 +210,7 @@ const API_SPEC: readonly EndpointInfo[] = [
153
210
  summary: 'Revoke an API key by ID',
154
211
  },
155
212
  {
213
+ agentProhibited: true,
156
214
  category: 'account',
157
215
  free: true,
158
216
  method: 'POST',
@@ -487,6 +545,57 @@ const API_SPEC: readonly EndpointInfo[] = [
487
545
  responseShape: RESPONSE_SUCCESS,
488
546
  summary: 'Delete a monitor and stop tracking',
489
547
  },
548
+ {
549
+ category: 'monitoring',
550
+ free: true,
551
+ method: 'GET',
552
+ path: '/api/v1/monitors/keywords',
553
+ responseShape: '{ monitors: [{ id, query, eventTypes, isActive, createdAt }], total }',
554
+ summary: 'List all keyword monitors',
555
+ },
556
+ {
557
+ category: 'monitoring',
558
+ free: false,
559
+ method: 'POST',
560
+ parameters: [
561
+ { description: 'Keyword, phrase, or X search query to monitor', in: 'body', name: 'query', required: true, type: 'string' },
562
+ PARAM_EVENT_TYPES_REQUIRED,
563
+ ],
564
+ path: '/api/v1/monitors/keywords',
565
+ responseShape: '{ id, query, eventTypes, isActive, createdAt }',
566
+ summary: 'Create an instant keyword monitor. Active monitors cost 21 credits per hour.',
567
+ },
568
+ {
569
+ category: 'monitoring',
570
+ free: true,
571
+ method: 'GET',
572
+ parameters: [PARAM_KEYWORD_MONITOR_ID],
573
+ path: '/api/v1/monitors/keywords/:id',
574
+ responseShape: '{ id, query, eventTypes, isActive, createdAt }',
575
+ summary: 'Get keyword monitor details by ID',
576
+ },
577
+ {
578
+ category: 'monitoring',
579
+ free: true,
580
+ method: 'PATCH',
581
+ parameters: [
582
+ PARAM_KEYWORD_MONITOR_ID,
583
+ { description: 'Set active or paused', in: 'body', name: 'isActive', required: false, type: 'boolean' },
584
+ PARAM_EVENT_TYPES_OPTIONAL,
585
+ ],
586
+ path: '/api/v1/monitors/keywords/:id',
587
+ responseShape: '{ id, query, eventTypes, isActive, createdAt }',
588
+ summary: 'Update keyword monitor settings or toggle active state',
589
+ },
590
+ {
591
+ category: 'monitoring',
592
+ free: true,
593
+ method: 'DELETE',
594
+ parameters: [PARAM_KEYWORD_MONITOR_ID],
595
+ path: '/api/v1/monitors/keywords/:id',
596
+ responseShape: RESPONSE_SUCCESS,
597
+ summary: 'Delete a keyword monitor and stop tracking',
598
+ },
490
599
  {
491
600
  category: 'monitoring',
492
601
  free: true,
@@ -581,7 +690,7 @@ const API_SPEC: readonly EndpointInfo[] = [
581
690
  parameters: [
582
691
  { description: 'Tweet ID to look up', in: 'path', name: 'tweetId', required: true, type: 'string' },
583
692
  ],
584
- mpp: { intent: 'charge', price: '$0.00015/call' },
693
+ mpp: { intent: 'charge', price: MPP_PRICE_CALL },
585
694
  path: '/api/v1/x/tweets/:tweetId',
586
695
  responseShape: '{ tweet: { id, text, likeCount, retweetCount, replyCount, viewCount, ... }, author? }',
587
696
  summary: 'Look up a single tweet with engagement metrics',
@@ -594,7 +703,7 @@ const API_SPEC: readonly EndpointInfo[] = [
594
703
  { description: 'Search query (X search syntax)', in: 'query', name: 'q', required: true, type: 'string' },
595
704
  { description: 'Max tweets to return (default 20, max 200)', in: 'query', name: 'limit', required: false, type: 'number' },
596
705
  ],
597
- mpp: { intent: 'session', price: '$0.00015/tweet' },
706
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
598
707
  path: '/api/v1/x/tweets/search',
599
708
  responseShape: '{ tweets: [{ id, text, author?, likeCount?, retweetCount?, media? }], total }',
600
709
  summary: 'Search tweets by query with optional limit for pagination',
@@ -606,7 +715,7 @@ const API_SPEC: readonly EndpointInfo[] = [
606
715
  parameters: [
607
716
  { description: 'X username to look up', in: 'path', name: 'username', required: true, type: 'string' },
608
717
  ],
609
- mpp: { intent: 'charge', price: '$0.00015/call' },
718
+ mpp: { intent: 'charge', price: MPP_PRICE_CALL },
610
719
  path: '/api/v1/x/users/:username',
611
720
  responseShape: '{ id, username, name, followers?, following?, verified?, description? }',
612
721
  summary: 'Get X user profile by username',
@@ -619,7 +728,7 @@ const API_SPEC: readonly EndpointInfo[] = [
619
728
  { description: 'Source username', in: 'query', name: 'source', required: true, type: 'string' },
620
729
  { description: 'Target username', in: 'query', name: 'target', required: true, type: 'string' },
621
730
  ],
622
- mpp: { intent: 'charge', price: '$0.00105/call' },
731
+ mpp: { intent: 'charge', price: MPP_PRICE_FOLLOW_CHECK },
623
732
  path: '/api/v1/x/followers/check',
624
733
  responseShape: '{ isFollowing, isFollowedBy, sourceUsername, targetUsername }',
625
734
  summary: 'Check follow relationship between two users',
@@ -631,7 +740,7 @@ const API_SPEC: readonly EndpointInfo[] = [
631
740
  parameters: [
632
741
  { description: 'Tweet ID of the X Article', in: 'path', name: 'tweetId', required: true, type: 'string' },
633
742
  ],
634
- mpp: { intent: 'charge', price: '$0.00105/call' },
743
+ mpp: { intent: 'charge', price: MPP_PRICE_FOLLOW_CHECK },
635
744
  path: '/api/v1/x/articles/:tweetId',
636
745
  responseShape: '{ article: { title, previewText, coverImageUrl, contents, createdAt, likeCount, replyCount, quoteCount, viewCount }, author? }',
637
746
  summary: 'Get full content of an X Article (long-form post) by tweet ID',
@@ -646,7 +755,7 @@ const API_SPEC: readonly EndpointInfo[] = [
646
755
  { description: 'Tweet URL or ID (single tweet)', in: 'body', name: 'tweetInput', required: false, type: 'string' },
647
756
  { description: 'Array of tweet URLs or IDs (bulk, max 50)', in: 'body', name: 'tweetIds', required: false, type: 'string[]' },
648
757
  ],
649
- mpp: { intent: 'session', price: '$0.00015/media' },
758
+ mpp: { intent: 'session', price: MPP_PRICE_MEDIA },
650
759
  path: '/api/v1/x/media/download',
651
760
  responseShape: 'Single: { tweetId, galleryUrl, cacheHit }. Bulk: { galleryUrl, totalTweets, totalMedia }',
652
761
  summary: 'Download media from tweets. Single tweetInput or bulk tweetIds. Returns gallery URL.',
@@ -661,7 +770,7 @@ const API_SPEC: readonly EndpointInfo[] = [
661
770
  { description: 'WOEID location ID (1 for worldwide)', in: 'query', name: 'woeid', required: false, type: 'number' },
662
771
  { description: 'Max number of trends', in: 'query', name: 'count', required: false, type: 'number' },
663
772
  ],
664
- mpp: { intent: 'charge', price: '$0.00045/call' },
773
+ mpp: { intent: 'charge', price: MPP_PRICE_TREND },
665
774
  path: '/api/v1/trends',
666
775
  responseShape: '{ trends: [{ name, query?, description?, rank? }], total, woeid }',
667
776
  summary: 'Get current trending topics on X',
@@ -674,11 +783,344 @@ const API_SPEC: readonly EndpointInfo[] = [
674
783
  { description: 'WOEID location ID (1 for worldwide)', in: 'query', name: 'woeid', required: false, type: 'number' },
675
784
  { description: 'Max number of trends', in: 'query', name: 'count', required: false, type: 'number' },
676
785
  ],
677
- mpp: { intent: 'charge', price: '$0.00045/call' },
786
+ mpp: { intent: 'charge', price: MPP_PRICE_TREND },
678
787
  path: '/api/v1/x/trends',
679
788
  responseShape: '{ trends: [{ name, query?, description?, rank? }], count, woeid }',
680
789
  summary: 'Get X trending topics by region',
681
790
  },
791
+ {
792
+ category: 'twitter',
793
+ free: false,
794
+ method: 'GET',
795
+ parameters: [PARAM_TWEET_ID, PARAM_CURSOR],
796
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
797
+ path: '/api/v1/x/tweets/:id/favoriters',
798
+ responseShape: RESPONSE_USERS_PAGINATED,
799
+ summary: 'Get users who liked a tweet. Returns about 20 per page.',
800
+ },
801
+ {
802
+ category: 'twitter',
803
+ free: false,
804
+ method: 'GET',
805
+ parameters: [PARAM_USER_ID, PARAM_CURSOR],
806
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
807
+ path: '/api/v1/x/users/:id/likes',
808
+ responseShape: RESPONSE_TWEETS_PAGINATED,
809
+ summary: 'Get tweets liked by a user. Returns about 20 per page.',
810
+ },
811
+ {
812
+ category: 'twitter',
813
+ free: false,
814
+ method: 'GET',
815
+ parameters: [PARAM_USER_ID, PARAM_CURSOR],
816
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
817
+ path: '/api/v1/x/users/:id/media',
818
+ responseShape: RESPONSE_TWEETS_PAGINATED,
819
+ summary: 'Get media tweets by a user. Returns about 20 per page.',
820
+ },
821
+ {
822
+ category: 'twitter',
823
+ free: false,
824
+ method: 'GET',
825
+ parameters: [PARAM_USER_ID, PARAM_CURSOR],
826
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
827
+ path: '/api/v1/x/users/:id/followers-you-know',
828
+ responseShape: RESPONSE_USERS_PAGINATED,
829
+ summary: 'Get followers you know for a user. Returns about 20 per page.',
830
+ },
831
+ {
832
+ category: 'twitter',
833
+ free: false,
834
+ method: 'GET',
835
+ parameters: [
836
+ { description: 'Optional bookmark folder ID', in: 'query', name: 'folderId', required: false, type: 'string' },
837
+ PARAM_CURSOR,
838
+ ],
839
+ path: '/api/v1/x/bookmarks',
840
+ responseShape: RESPONSE_TWEETS_PAGINATED,
841
+ sensitive: true,
842
+ summary: 'Get bookmarked tweets. Requires explicit user request.',
843
+ },
844
+ {
845
+ category: 'twitter',
846
+ free: false,
847
+ method: 'GET',
848
+ parameters: [PARAM_CURSOR],
849
+ path: '/api/v1/x/bookmarks/folders',
850
+ responseShape: '{ folders: [{ id, name }], has_more, next_cursor }',
851
+ sensitive: true,
852
+ summary: 'Get bookmark folders. Requires explicit user request.',
853
+ },
854
+ {
855
+ category: 'twitter',
856
+ free: false,
857
+ method: 'GET',
858
+ parameters: [
859
+ { description: 'Notification type filter: All, Verified, Mentions', in: 'query', name: 'type', required: false, type: 'string' },
860
+ PARAM_CURSOR,
861
+ ],
862
+ path: '/api/v1/x/notifications',
863
+ responseShape: '{ notifications: [{ id, type?, message?, timestamp? }], has_more, next_cursor }',
864
+ sensitive: true,
865
+ summary: 'Get notifications. Requires explicit user request.',
866
+ },
867
+ {
868
+ category: 'twitter',
869
+ free: false,
870
+ method: 'GET',
871
+ parameters: [
872
+ { description: 'Comma-separated tweet IDs to exclude from results', in: 'query', name: 'seenTweetIds', required: false, type: 'string' },
873
+ PARAM_CURSOR,
874
+ ],
875
+ path: '/api/v1/x/timeline',
876
+ responseShape: RESPONSE_TWEETS_PAGINATED,
877
+ sensitive: true,
878
+ summary: 'Get home timeline. Requires explicit user request.',
879
+ },
880
+ {
881
+ category: 'twitter',
882
+ free: false,
883
+ method: 'GET',
884
+ parameters: [
885
+ { description: 'Target user ID', in: 'path', name: 'userId', required: true, type: 'string' },
886
+ { description: 'Connected X account username without @', in: 'query', name: 'account', required: true, type: 'string' },
887
+ PARAM_CURSOR,
888
+ ],
889
+ path: '/api/v1/x/dm/:userId/history',
890
+ responseShape: '{ messages: [{ id, text?, sender_id?, receiver_id?, created?, media_url? }], has_more, next_cursor }',
891
+ sensitive: true,
892
+ summary: 'Get DM conversation history. Requires explicit user request.',
893
+ },
894
+ {
895
+ category: 'twitter',
896
+ free: false,
897
+ method: 'GET',
898
+ parameters: [
899
+ PARAM_USER_ID,
900
+ PARAM_CURSOR,
901
+ { description: 'Include replies (default false)', in: 'query', name: 'includeReplies', required: false, type: 'boolean' },
902
+ { description: 'Include parent tweet for replies (default false)', in: 'query', name: 'includeParentTweet', required: false, type: 'boolean' },
903
+ ],
904
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
905
+ path: '/api/v1/x/users/:id/tweets',
906
+ responseShape: RESPONSE_TWEETS_PAGINATED,
907
+ summary: 'Get latest tweets by a user. Preferred over search for user timelines.',
908
+ },
909
+ {
910
+ category: 'twitter',
911
+ free: false,
912
+ method: 'GET',
913
+ parameters: [PARAM_COMMUNITY_ID],
914
+ mpp: { intent: 'charge', price: MPP_PRICE_CALL },
915
+ path: '/api/v1/x/communities/:id/info',
916
+ responseShape: '{ community: { id, name?, description?, member_count?, moderator_count?, created?, banner_url?, join_policy?, rules? } }',
917
+ summary: 'Get community details.',
918
+ },
919
+ {
920
+ category: 'twitter',
921
+ free: false,
922
+ method: 'GET',
923
+ parameters: [PARAM_COMMUNITY_ID, PARAM_CURSOR, PARAM_PAGE_SIZE_20],
924
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
925
+ path: '/api/v1/x/communities/:id/members',
926
+ responseShape: RESPONSE_USERS_PAGINATED,
927
+ summary: 'Get community members. Use cursor for pagination.',
928
+ },
929
+ {
930
+ category: 'twitter',
931
+ free: false,
932
+ method: 'GET',
933
+ parameters: [PARAM_COMMUNITY_ID, PARAM_CURSOR],
934
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
935
+ path: '/api/v1/x/communities/:id/moderators',
936
+ responseShape: RESPONSE_USERS_PAGINATED,
937
+ summary: 'Get community moderators. Returns about 20 per page.',
938
+ },
939
+ {
940
+ category: 'twitter',
941
+ free: false,
942
+ method: 'GET',
943
+ parameters: [PARAM_COMMUNITY_ID, PARAM_CURSOR],
944
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
945
+ path: '/api/v1/x/communities/:id/tweets',
946
+ responseShape: RESPONSE_TWEETS_PAGINATED,
947
+ summary: 'Get community tweets. Returns about 20 per page.',
948
+ },
949
+ {
950
+ category: 'twitter',
951
+ free: false,
952
+ method: 'GET',
953
+ parameters: [PARAM_SEARCH_QUERY, PARAM_QUERY_TYPE, PARAM_CURSOR],
954
+ mpp: { intent: 'session', price: MPP_PRICE_COMMUNITY },
955
+ path: '/api/v1/x/communities/search',
956
+ responseShape: RESPONSE_TWEETS_PAGINATED,
957
+ summary: 'Search tweets across all communities.',
958
+ },
959
+ {
960
+ category: 'twitter',
961
+ free: false,
962
+ method: 'GET',
963
+ parameters: [PARAM_SEARCH_QUERY, PARAM_QUERY_TYPE, PARAM_CURSOR],
964
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
965
+ path: '/api/v1/x/communities/tweets',
966
+ responseShape: RESPONSE_TWEETS_PAGINATED,
967
+ summary: 'Get tweets from all communities matching a query.',
968
+ },
969
+ {
970
+ category: 'twitter',
971
+ free: false,
972
+ method: 'GET',
973
+ parameters: [PARAM_LIST_ID, PARAM_CURSOR],
974
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
975
+ path: '/api/v1/x/lists/:id/followers',
976
+ responseShape: RESPONSE_USERS_PAGINATED,
977
+ summary: 'Get list followers.',
978
+ },
979
+ {
980
+ category: 'twitter',
981
+ free: false,
982
+ method: 'GET',
983
+ parameters: [PARAM_LIST_ID, PARAM_CURSOR, PARAM_PAGE_SIZE_20],
984
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
985
+ path: '/api/v1/x/lists/:id/members',
986
+ responseShape: RESPONSE_USERS_PAGINATED,
987
+ summary: 'Get list members.',
988
+ },
989
+ {
990
+ category: 'twitter',
991
+ free: false,
992
+ method: 'GET',
993
+ parameters: [
994
+ PARAM_LIST_ID,
995
+ PARAM_CURSOR,
996
+ PARAM_SINCE_TIME,
997
+ PARAM_UNTIL_TIME,
998
+ { description: 'Include replies (default false)', in: 'query', name: 'includeReplies', required: false, type: 'boolean' },
999
+ ],
1000
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
1001
+ path: '/api/v1/x/lists/:id/tweets',
1002
+ responseShape: RESPONSE_TWEETS_PAGINATED,
1003
+ summary: 'Get list tweets. Returns about 20 per page.',
1004
+ },
1005
+ {
1006
+ category: 'twitter',
1007
+ free: false,
1008
+ method: 'GET',
1009
+ parameters: [
1010
+ { description: 'Comma-separated tweet IDs (max 100)', in: 'query', name: 'ids', required: true, type: 'string' },
1011
+ ],
1012
+ path: '/api/v1/x/tweets',
1013
+ responseShape: `{ tweets: [${RESPONSE_TWEET_BASIC}], has_more: false, next_cursor: "" }`,
1014
+ summary: 'Get multiple tweets by IDs. Max 100 IDs per request.',
1015
+ },
1016
+ {
1017
+ category: 'twitter',
1018
+ free: false,
1019
+ method: 'GET',
1020
+ parameters: [
1021
+ PARAM_TWEET_ID,
1022
+ PARAM_CURSOR,
1023
+ PARAM_SINCE_TIME,
1024
+ PARAM_UNTIL_TIME,
1025
+ { description: 'Include replies (default true)', in: 'query', name: 'includeReplies', required: false, type: 'boolean' },
1026
+ ],
1027
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
1028
+ path: '/api/v1/x/tweets/:id/quotes',
1029
+ responseShape: RESPONSE_TWEETS_PAGINATED,
1030
+ summary: 'Get quote tweets of a tweet.',
1031
+ },
1032
+ {
1033
+ category: 'twitter',
1034
+ free: false,
1035
+ method: 'GET',
1036
+ parameters: [PARAM_TWEET_ID, PARAM_CURSOR, PARAM_SINCE_TIME, PARAM_UNTIL_TIME],
1037
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
1038
+ path: '/api/v1/x/tweets/:id/replies',
1039
+ responseShape: RESPONSE_TWEETS_PAGINATED,
1040
+ summary: 'Get replies to a tweet.',
1041
+ },
1042
+ {
1043
+ category: 'twitter',
1044
+ free: false,
1045
+ method: 'GET',
1046
+ parameters: [PARAM_TWEET_ID, PARAM_CURSOR],
1047
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
1048
+ path: '/api/v1/x/tweets/:id/retweeters',
1049
+ responseShape: RESPONSE_USERS_PAGINATED,
1050
+ summary: 'Get users who retweeted a tweet.',
1051
+ },
1052
+ {
1053
+ category: 'twitter',
1054
+ free: false,
1055
+ method: 'GET',
1056
+ parameters: [PARAM_TWEET_ID, PARAM_CURSOR],
1057
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
1058
+ path: '/api/v1/x/tweets/:id/thread',
1059
+ responseShape: RESPONSE_TWEETS_PAGINATED,
1060
+ summary: 'Get thread context for a tweet.',
1061
+ },
1062
+ {
1063
+ category: 'twitter',
1064
+ free: false,
1065
+ method: 'GET',
1066
+ parameters: [
1067
+ { description: 'Comma-separated user IDs (max 100)', in: 'query', name: 'ids', required: true, type: 'string' },
1068
+ ],
1069
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
1070
+ path: '/api/v1/x/users/batch',
1071
+ responseShape: `{ users: [${RESPONSE_USER}] }`,
1072
+ summary: 'Get multiple users by IDs. Max 100 IDs per request.',
1073
+ },
1074
+ {
1075
+ category: 'twitter',
1076
+ free: false,
1077
+ method: 'GET',
1078
+ parameters: [PARAM_SEARCH_QUERY, PARAM_CURSOR],
1079
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
1080
+ path: '/api/v1/x/users/search',
1081
+ responseShape: RESPONSE_USERS_PAGINATED,
1082
+ summary: 'Search users by name or username.',
1083
+ },
1084
+ {
1085
+ category: 'twitter',
1086
+ free: false,
1087
+ method: 'GET',
1088
+ parameters: [PARAM_USER_ID, PARAM_CURSOR, PARAM_AFTER_ALIAS, PARAM_PAGE_SIZE_200, PARAM_LIMIT_ALIAS],
1089
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
1090
+ path: '/api/v1/x/users/:id/followers',
1091
+ responseShape: RESPONSE_USERS_PAGINATED,
1092
+ summary: 'Get user followers. Use cursor for pagination.',
1093
+ },
1094
+ {
1095
+ category: 'twitter',
1096
+ free: false,
1097
+ method: 'GET',
1098
+ parameters: [PARAM_USER_ID, PARAM_CURSOR, PARAM_AFTER_ALIAS, PARAM_PAGE_SIZE_200, PARAM_LIMIT_ALIAS],
1099
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
1100
+ path: '/api/v1/x/users/:id/following',
1101
+ responseShape: RESPONSE_USERS_PAGINATED,
1102
+ summary: 'Get users this user follows. Use cursor for pagination.',
1103
+ },
1104
+ {
1105
+ category: 'twitter',
1106
+ free: false,
1107
+ method: 'GET',
1108
+ parameters: [PARAM_USER_ID, PARAM_CURSOR, PARAM_SINCE_TIME, PARAM_UNTIL_TIME],
1109
+ mpp: { intent: 'session', price: MPP_PRICE_TWEET },
1110
+ path: '/api/v1/x/users/:id/mentions',
1111
+ responseShape: RESPONSE_TWEETS_PAGINATED,
1112
+ summary: 'Get tweets mentioning a user.',
1113
+ },
1114
+ {
1115
+ category: 'twitter',
1116
+ free: false,
1117
+ method: 'GET',
1118
+ parameters: [PARAM_USER_ID, PARAM_CURSOR],
1119
+ mpp: { intent: 'session', price: MPP_PRICE_USER },
1120
+ path: '/api/v1/x/users/:id/verified-followers',
1121
+ responseShape: RESPONSE_USERS_PAGINATED,
1122
+ summary: 'Get verified followers.',
1123
+ },
682
1124
 
683
1125
 
684
1126
  // --- X Account Management ---
@@ -706,6 +1148,7 @@ const API_SPEC: readonly EndpointInfo[] = [
706
1148
  summary: 'Connect X account (dashboard only - agent-prohibited)',
707
1149
  },
708
1150
  {
1151
+ agentProhibited: true,
709
1152
  category: CATEGORY_X_ACCOUNTS,
710
1153
  free: true,
711
1154
  method: 'GET',
@@ -715,6 +1158,7 @@ const API_SPEC: readonly EndpointInfo[] = [
715
1158
  summary: 'Get X account details',
716
1159
  },
717
1160
  {
1161
+ agentProhibited: true,
718
1162
  category: CATEGORY_X_ACCOUNTS,
719
1163
  free: true,
720
1164
  method: 'DELETE',
@@ -737,6 +1181,15 @@ const API_SPEC: readonly EndpointInfo[] = [
737
1181
  responseShape: '{ id, xUsername, status }',
738
1182
  summary: 'Re-authenticate X account (dashboard only - agent-prohibited)',
739
1183
  },
1184
+ {
1185
+ agentProhibited: true,
1186
+ category: CATEGORY_X_ACCOUNTS,
1187
+ free: true,
1188
+ method: 'POST',
1189
+ path: '/api/v1/x/accounts/bulk-retry',
1190
+ responseShape: '{ cleared }',
1191
+ summary: 'Bulk retry temporarily failed X accounts (dashboard only - agent-prohibited)',
1192
+ },
740
1193
 
741
1194
  // --- X Write Actions ---
742
1195
  {
@@ -792,6 +1245,15 @@ const API_SPEC: readonly EndpointInfo[] = [
792
1245
  responseShape: RESPONSE_SUCCESS,
793
1246
  summary: 'Retweet',
794
1247
  },
1248
+ {
1249
+ category: CATEGORY_X_WRITE,
1250
+ free: false,
1251
+ method: 'DELETE',
1252
+ parameters: PARAMS_TWEET_ACTION,
1253
+ path: '/api/v1/x/tweets/:id/retweet',
1254
+ responseShape: RESPONSE_SUCCESS,
1255
+ summary: 'Unretweet',
1256
+ },
795
1257
  {
796
1258
  category: CATEGORY_X_WRITE,
797
1259
  free: false,
@@ -937,6 +1399,7 @@ const API_SPEC: readonly EndpointInfo[] = [
937
1399
 
938
1400
  // --- Support ---
939
1401
  {
1402
+ agentProhibited: true,
940
1403
  category: CATEGORY_SUPPORT,
941
1404
  free: true,
942
1405
  method: 'POST',
@@ -949,6 +1412,7 @@ const API_SPEC: readonly EndpointInfo[] = [
949
1412
  summary: 'Open a new support ticket',
950
1413
  },
951
1414
  {
1415
+ agentProhibited: true,
952
1416
  category: CATEGORY_SUPPORT,
953
1417
  free: true,
954
1418
  method: 'GET',
@@ -957,6 +1421,7 @@ const API_SPEC: readonly EndpointInfo[] = [
957
1421
  summary: 'List your support tickets',
958
1422
  },
959
1423
  {
1424
+ agentProhibited: true,
960
1425
  category: CATEGORY_SUPPORT,
961
1426
  free: true,
962
1427
  method: 'GET',
@@ -966,6 +1431,7 @@ const API_SPEC: readonly EndpointInfo[] = [
966
1431
  summary: 'Get a ticket with message history',
967
1432
  },
968
1433
  {
1434
+ agentProhibited: true,
969
1435
  category: CATEGORY_SUPPORT,
970
1436
  free: true,
971
1437
  method: 'PATCH',
@@ -978,6 +1444,7 @@ const API_SPEC: readonly EndpointInfo[] = [
978
1444
  summary: 'Update ticket status',
979
1445
  },
980
1446
  {
1447
+ agentProhibited: true,
981
1448
  category: CATEGORY_SUPPORT,
982
1449
  free: true,
983
1450
  method: 'POST',
@@ -991,10 +1458,8 @@ const API_SPEC: readonly EndpointInfo[] = [
991
1458
  },
992
1459
 
993
1460
  // --- Credits ---
994
- // All /api/v1/credits* endpoints are free. They expose the PAYG
995
- // top-up path and balance read without requiring an active subscription.
996
- // Agents should offer these when an unsubscribed user hits a 402 on a
997
- // paid endpoint.
1461
+ // Balance reads stay agent-callable. Checkout and saved-card charge
1462
+ // endpoints remain documented but are dashboard-only for agent safety.
998
1463
  {
999
1464
  category: 'credits',
1000
1465
  free: true,
@@ -1004,6 +1469,7 @@ const API_SPEC: readonly EndpointInfo[] = [
1004
1469
  summary: 'Get credits balance',
1005
1470
  },
1006
1471
  {
1472
+ agentProhibited: true,
1007
1473
  category: 'credits',
1008
1474
  free: true,
1009
1475
  method: 'POST',
@@ -1015,6 +1481,7 @@ const API_SPEC: readonly EndpointInfo[] = [
1015
1481
  summary: 'Top up credits via Stripe Checkout. $10 min.',
1016
1482
  },
1017
1483
  {
1484
+ agentProhibited: true,
1018
1485
  category: 'credits',
1019
1486
  free: true,
1020
1487
  method: 'GET',
@@ -1026,6 +1493,7 @@ const API_SPEC: readonly EndpointInfo[] = [
1026
1493
  summary: 'Check credit top-up checkout status.',
1027
1494
  },
1028
1495
  {
1496
+ agentProhibited: true,
1029
1497
  category: 'credits',
1030
1498
  free: true,
1031
1499
  method: 'POST',