@xquik/tweetclaw 1.0.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.
@@ -0,0 +1,1024 @@
1
+ import type { EndpointInfo, EndpointParameter } from './types.js';
2
+
3
+ const RESPONSE_SUCCESS = '{ success: true }';
4
+ const DESCRIPTION_PAGINATION_CURSOR = 'Pagination cursor';
5
+ const DESCRIPTION_STYLE_USERNAME = 'X username of cached style';
6
+ const DESCRIPTION_EXPORT_FORMAT = 'Export format (csv, xlsx, md)';
7
+ const CATEGORY_BOT = 'bot';
8
+ const DESCRIPTION_PLATFORM_USER_ID = 'Platform user ID';
9
+ const CATEGORY_INTEGRATIONS = 'integrations';
10
+ const CATEGORY_X_ACCOUNTS = 'x-accounts';
11
+
12
+ const PAGINATION_PARAMS: readonly EndpointParameter[] = [
13
+ { description: 'Max items per page', in: 'query', name: 'limit', required: false, type: 'number' },
14
+ { description: DESCRIPTION_PAGINATION_CURSOR, in: 'query', name: 'after', required: false, type: 'string' },
15
+ ];
16
+
17
+ const EXTRACTION_SEARCH_PARAMS: readonly EndpointParameter[] = [
18
+ { description: 'Filter tweets by author username (tweet_search_extractor)', in: 'body', name: 'fromUser', required: false, type: 'string' },
19
+ { description: 'Filter tweets to a specific user (tweet_search_extractor)', in: 'body', name: 'toUser', required: false, type: 'string' },
20
+ { description: 'Filter tweets mentioning a user (tweet_search_extractor)', in: 'body', name: 'mentioning', required: false, type: 'string' },
21
+ { description: 'Language code filter, e.g. en, tr (tweet_search_extractor)', in: 'body', name: 'language', required: false, type: 'string' },
22
+ { description: 'Start date YYYY-MM-DD (tweet_search_extractor)', in: 'body', name: 'sinceDate', required: false, type: 'string' },
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' },
25
+ { description: 'Minimum likes threshold (tweet_search_extractor)', in: 'body', name: 'minFaves', required: false, type: 'number' },
26
+ { description: 'Minimum retweets threshold (tweet_search_extractor)', in: 'body', name: 'minRetweets', required: false, type: 'number' },
27
+ { description: 'Minimum replies threshold (tweet_search_extractor)', in: 'body', name: 'minReplies', required: false, type: 'number' },
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' },
31
+ ];
32
+
33
+ const PARAM_STYLE_USERNAME: EndpointParameter =
34
+ { description: DESCRIPTION_STYLE_USERNAME, in: 'path', name: 'username', required: true, type: 'string' };
35
+
36
+ const PARAM_EXPORT_FORMAT: EndpointParameter =
37
+ { description: DESCRIPTION_EXPORT_FORMAT, in: 'query', name: 'format', required: false, type: 'string' };
38
+
39
+ const PARAM_DRAW_ID: EndpointParameter =
40
+ { description: 'Draw public ID', in: 'path', name: 'id', required: true, type: 'string' };
41
+
42
+ const PARAM_EXTRACTION_ID: EndpointParameter =
43
+ { description: 'Extraction public ID', in: 'path', name: 'id', required: true, type: 'string' };
44
+
45
+ const PARAM_INTEGRATION_ID: EndpointParameter =
46
+ { description: 'Integration ID', in: 'path', name: 'id', required: true, type: 'string' };
47
+
48
+ const PARAM_X_ACCOUNT: EndpointParameter =
49
+ { description: 'X account (@username or account ID)', in: 'body', name: 'account', required: true, type: 'string' };
50
+
51
+ const PARAM_X_ACCOUNT_ID: EndpointParameter =
52
+ { description: 'X account ID', in: 'path', name: 'id', required: true, type: 'string' };
53
+
54
+ const DESCRIPTION_EVENT_TYPES = 'tweet.new, tweet.reply, tweet.quote, tweet.retweet, follower.gained, follower.lost';
55
+
56
+ const PARAM_EVENT_TYPES_REQUIRED: EndpointParameter =
57
+ { description: `Event types: ${DESCRIPTION_EVENT_TYPES}`, in: 'body', name: 'eventTypes', required: true, type: 'string[]' };
58
+
59
+ const PARAM_EVENT_TYPES_OPTIONAL: EndpointParameter =
60
+ { description: `Updated event types: ${DESCRIPTION_EVENT_TYPES}`, in: 'body', name: 'eventTypes', required: false, type: 'string[]' };
61
+
62
+ const PARAM_MONITOR_ID: EndpointParameter =
63
+ { description: 'Monitor ID', in: 'path', name: 'id', required: true, type: 'string' };
64
+
65
+ const PARAM_WEBHOOK_ID: EndpointParameter =
66
+ { description: 'Webhook ID', in: 'path', name: 'id', required: true, type: 'string' };
67
+
68
+ const PARAM_TWEET_ID: EndpointParameter =
69
+ { description: 'Tweet ID', in: 'path', name: 'id', required: true, type: 'string' };
70
+
71
+ const PARAM_COMMUNITY_ID: EndpointParameter =
72
+ { description: 'Community ID', in: 'path', name: 'id', required: true, type: 'string' };
73
+
74
+ const PARAMS_TWEET_ACTION: readonly EndpointParameter[] = [PARAM_TWEET_ID, PARAM_X_ACCOUNT];
75
+ const PARAMS_COMMUNITY_ACTION: readonly EndpointParameter[] = [PARAM_COMMUNITY_ID, PARAM_X_ACCOUNT];
76
+
77
+ const PARAM_USER_ID_FOLLOW: EndpointParameter =
78
+ { description: 'User ID to follow', in: 'path', name: 'id', required: true, type: 'string' };
79
+
80
+ const PARAM_USER_ID_UNFOLLOW: EndpointParameter =
81
+ { description: 'User ID to unfollow', in: 'path', name: 'id', required: true, type: 'string' };
82
+
83
+ const PARAM_MEDIA_URL: EndpointParameter =
84
+ { description: 'URL to download media from (alternative to file, HTTPS only)', in: 'body', name: 'url', required: false, type: 'string' };
85
+
86
+ const RESPONSE_COMMUNITY_ACTION = '{ communityId, communityName, success: true }';
87
+ const CATEGORY_X_WRITE = 'x-write';
88
+
89
+ const API_SPEC: readonly EndpointInfo[] = [
90
+ // --- Account ---
91
+ {
92
+ category: 'account',
93
+ free: true,
94
+ method: 'GET',
95
+ path: '/api/v1/account',
96
+ responseShape: '{ email, locale, xUsername, subscription, usage }',
97
+ summary: 'Get current account info and subscription status',
98
+ },
99
+ {
100
+ category: 'account',
101
+ free: true,
102
+ method: 'PATCH',
103
+ parameters: [
104
+ { description: 'Locale code (en, tr, es)', in: 'body', name: 'locale', required: true, type: 'string' },
105
+ ],
106
+ path: '/api/v1/account',
107
+ responseShape: RESPONSE_SUCCESS,
108
+ summary: 'Update account settings such as locale',
109
+ },
110
+ {
111
+ category: 'account',
112
+ free: true,
113
+ method: 'PUT',
114
+ parameters: [
115
+ { description: 'X username without @', in: 'body', name: 'username', required: true, type: 'string' },
116
+ ],
117
+ path: '/api/v1/account/x-identity',
118
+ responseShape: '{ success: true, xUsername }',
119
+ summary: 'Set or update linked X username',
120
+ },
121
+ {
122
+ category: 'account',
123
+ free: true,
124
+ method: 'GET',
125
+ path: '/api/v1/api-keys',
126
+ responseShape: '{ keys: [{ id, name, prefix, isActive, createdAt, lastUsedAt? }] }',
127
+ summary: 'List all API keys for the account',
128
+ },
129
+ {
130
+ category: 'account',
131
+ free: true,
132
+ method: 'POST',
133
+ parameters: [
134
+ { description: 'Display name for the key', in: 'body', name: 'name', required: false, type: 'string' },
135
+ ],
136
+ path: '/api/v1/api-keys',
137
+ responseShape: '{ id, name, prefix, fullKey, createdAt }',
138
+ summary: 'Create a new API key',
139
+ },
140
+ {
141
+ category: 'account',
142
+ free: true,
143
+ method: 'DELETE',
144
+ parameters: [
145
+ { description: 'API key ID to revoke', in: 'path', name: 'id', required: true, type: 'string' },
146
+ ],
147
+ path: '/api/v1/api-keys/:id',
148
+ responseShape: RESPONSE_SUCCESS,
149
+ summary: 'Revoke an API key by ID',
150
+ },
151
+ {
152
+ category: 'account',
153
+ free: true,
154
+ method: 'POST',
155
+ path: '/api/v1/subscribe',
156
+ responseShape: '{ url }',
157
+ summary: 'Get Stripe checkout or billing portal URL',
158
+ },
159
+
160
+ // --- Composition ---
161
+ {
162
+ category: 'composition',
163
+ free: true,
164
+ method: 'POST',
165
+ parameters: [
166
+ { description: 'Workflow step: compose, refine, or score', in: 'body', name: 'step', required: true, type: 'string' },
167
+ { description: 'Tweet topic (compose, refine)', in: 'body', name: 'topic', required: false, type: 'string' },
168
+ { description: 'Optimization goal: engagement, followers, authority, conversation', in: 'body', name: 'goal', required: false, type: 'string' },
169
+ { description: 'Tweet draft text to evaluate (score)', in: 'body', name: 'draft', required: false, type: 'string' },
170
+ { description: 'Desired tone for the tweet (refine)', in: 'body', name: 'tone', required: false, type: 'string' },
171
+ { description: 'Cached style username for voice matching (compose)', in: 'body', name: 'styleUsername', required: false, type: 'string' },
172
+ { description: 'Extra context or URLs (refine)', in: 'body', name: 'additionalContext', required: false, type: 'string' },
173
+ { description: 'Desired call to action (refine)', in: 'body', name: 'callToAction', required: false, type: 'string' },
174
+ { description: 'Media type: photo, video, none (refine)', in: 'body', name: 'mediaType', required: false, type: 'string' },
175
+ { description: 'Whether a link is attached (score)', in: 'body', name: 'hasLink', required: false, type: 'boolean' },
176
+ { description: 'Whether media is attached (score)', in: 'body', name: 'hasMedia', required: false, type: 'boolean' },
177
+ ],
178
+ path: '/api/v1/compose',
179
+ responseShape: '{ contentRules, scorerWeights, followUpQuestions, ... }',
180
+ summary: 'Compose, refine, or score a tweet using algorithm data',
181
+ },
182
+ {
183
+ category: 'composition',
184
+ free: true,
185
+ method: 'GET',
186
+ parameters: [
187
+ { description: 'Max items to return', in: 'query', name: 'limit', required: false, type: 'number' },
188
+ { description: 'Cursor for pagination', in: 'query', name: 'afterCursor', required: false, type: 'string' },
189
+ ],
190
+ path: '/api/v1/drafts',
191
+ responseShape: '{ drafts: [{ id, text, topic?, goal?, createdAt }], hasMore, nextCursor? }',
192
+ summary: 'List saved tweet drafts with pagination',
193
+ },
194
+ {
195
+ category: 'composition',
196
+ free: true,
197
+ method: 'POST',
198
+ parameters: [
199
+ { description: 'Draft tweet text', in: 'body', name: 'text', required: true, type: 'string' },
200
+ { description: 'Tweet topic', in: 'body', name: 'topic', required: false, type: 'string' },
201
+ { description: 'Optimization goal: engagement, followers, authority, conversation', in: 'body', name: 'goal', required: false, type: 'string' },
202
+ ],
203
+ path: '/api/v1/drafts',
204
+ responseShape: '{ id, text, topic?, goal?, createdAt, updatedAt }',
205
+ summary: 'Save a new tweet draft',
206
+ },
207
+ {
208
+ category: 'composition',
209
+ free: true,
210
+ method: 'GET',
211
+ parameters: [
212
+ { description: 'Draft ID', in: 'path', name: 'id', required: true, type: 'string' },
213
+ ],
214
+ path: '/api/v1/drafts/:id',
215
+ responseShape: '{ id, text, topic?, goal?, createdAt, updatedAt }',
216
+ summary: 'Get a single draft by ID',
217
+ },
218
+ {
219
+ category: 'composition',
220
+ free: true,
221
+ method: 'DELETE',
222
+ parameters: [
223
+ { description: 'Draft ID to delete', in: 'path', name: 'id', required: true, type: 'string' },
224
+ ],
225
+ path: '/api/v1/drafts/:id',
226
+ responseShape: '204 No Content',
227
+ summary: 'Delete a draft by ID',
228
+ },
229
+ {
230
+ category: 'composition',
231
+ free: true,
232
+ method: 'GET',
233
+ path: '/api/v1/styles',
234
+ responseShape: '{ styles: [{ xUsername, tweetCount, isOwnAccount, fetchedAt }] }',
235
+ summary: 'List all cached writing style profiles',
236
+ },
237
+ {
238
+ category: 'composition',
239
+ free: true,
240
+ method: 'POST',
241
+ parameters: [
242
+ { description: 'X username to analyze', in: 'body', name: 'username', required: true, type: 'string' },
243
+ ],
244
+ path: '/api/v1/styles',
245
+ responseShape: '{ xUsername, tweetCount, isOwnAccount, fetchedAt, tweets }',
246
+ summary: 'Analyze and cache a writing style from recent tweets',
247
+ },
248
+ {
249
+ category: 'composition',
250
+ free: true,
251
+ method: 'GET',
252
+ parameters: [PARAM_STYLE_USERNAME],
253
+ path: '/api/v1/styles/:username',
254
+ responseShape: '{ xUsername, tweetCount, isOwnAccount, fetchedAt, tweets }',
255
+ summary: 'Get a cached style profile by username',
256
+ },
257
+ {
258
+ category: 'composition',
259
+ free: true,
260
+ method: 'PUT',
261
+ parameters: [
262
+ { description: 'Style label (username key)', in: 'path', name: 'username', required: true, type: 'string' },
263
+ { description: 'Display label for the style', in: 'body', name: 'label', required: true, type: 'string' },
264
+ { description: 'Array of tweet objects with text field', in: 'body', name: 'tweets', required: true, type: 'array' },
265
+ ],
266
+ path: '/api/v1/styles/:username',
267
+ responseShape: '{ xUsername, tweetCount, isOwnAccount, fetchedAt, tweets }',
268
+ summary: 'Create or update a style profile with custom tweets',
269
+ },
270
+ {
271
+ category: 'composition',
272
+ free: true,
273
+ method: 'DELETE',
274
+ parameters: [
275
+ { description: 'X username of style to delete', in: 'path', name: 'username', required: true, type: 'string' },
276
+ ],
277
+ path: '/api/v1/styles/:username',
278
+ responseShape: '204 No Content',
279
+ summary: 'Delete a cached style profile',
280
+ },
281
+ {
282
+ category: 'composition',
283
+ free: false,
284
+ method: 'GET',
285
+ parameters: [PARAM_STYLE_USERNAME],
286
+ path: '/api/v1/styles/:username/performance',
287
+ responseShape: '{ xUsername, tweetCount, tweets: [{ id, text, likeCount, retweetCount, ... }] }',
288
+ summary: 'Get engagement metrics for cached style tweets',
289
+ },
290
+ {
291
+ category: 'composition',
292
+ free: true,
293
+ method: 'GET',
294
+ parameters: [
295
+ { description: 'First username to compare', in: 'query', name: 'username1', required: true, type: 'string' },
296
+ { description: 'Second username to compare', in: 'query', name: 'username2', required: true, type: 'string' },
297
+ ],
298
+ path: '/api/v1/styles/compare',
299
+ responseShape: '{ style1: { xUsername, tweets, ... }, style2: { xUsername, tweets, ... } }',
300
+ summary: 'Compare two cached writing style profiles',
301
+ },
302
+ {
303
+ category: 'composition',
304
+ free: true,
305
+ method: 'GET',
306
+ parameters: [
307
+ { 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' },
312
+ ],
313
+ path: '/api/v1/radar',
314
+ responseShape: '{ items: [{ title, url?, score, category, source, region, publishedAt }], total }',
315
+ summary: 'Get trending topics from curated radar sources',
316
+ },
317
+
318
+ // --- Extraction ---
319
+ {
320
+ category: 'extraction',
321
+ free: false,
322
+ method: 'GET',
323
+ parameters: [...PAGINATION_PARAMS],
324
+ path: '/api/v1/draws',
325
+ responseShape: '{ draws: [{ id, tweetUrl, status, totalEntries, validEntries, createdAt }], hasMore, nextCursor? }',
326
+ summary: 'List giveaway draws with pagination',
327
+ },
328
+ {
329
+ category: 'extraction',
330
+ free: false,
331
+ method: 'POST',
332
+ parameters: [
333
+ { description: 'URL of the giveaway tweet', in: 'body', name: 'tweetUrl', required: true, type: 'string' },
334
+ { description: 'Number of winners to pick', in: 'body', name: 'winnerCount', required: false, type: 'number' },
335
+ { description: 'Winner eligibility filters (follow, like, retweet, etc.)', in: 'body', name: 'filters', required: false, type: 'object' },
336
+ ],
337
+ path: '/api/v1/draws',
338
+ responseShape: '{ id, tweetId, totalEntries, validEntries, winners: [{ position, authorUsername, tweetId, isBackup }] }',
339
+ summary: 'Run a giveaway draw on a tweet',
340
+ },
341
+ {
342
+ category: 'extraction',
343
+ free: false,
344
+ method: 'GET',
345
+ parameters: [PARAM_DRAW_ID],
346
+ path: '/api/v1/draws/:id',
347
+ responseShape: '{ draw: { id, tweetUrl, tweetId, status, totalEntries, validEntries, ... }, winners }',
348
+ summary: 'Get draw details and winners',
349
+ },
350
+ {
351
+ category: 'extraction',
352
+ free: false,
353
+ method: 'GET',
354
+ parameters: [PARAM_DRAW_ID, PARAM_EXPORT_FORMAT],
355
+ path: '/api/v1/draws/:id/export',
356
+ responseShape: 'CSV, XLSX, or Markdown file download',
357
+ summary: 'Export draw results as CSV, XLSX, or Markdown',
358
+ },
359
+ {
360
+ category: 'extraction',
361
+ free: false,
362
+ method: 'GET',
363
+ parameters: [
364
+ ...PAGINATION_PARAMS,
365
+ { description: 'Filter by tool type', in: 'query', name: 'toolType', required: false, type: 'string' },
366
+ { description: 'Filter by status (running, completed, failed)', in: 'query', name: 'status', required: false, type: 'string' },
367
+ ],
368
+ path: '/api/v1/extractions',
369
+ responseShape: '{ extractions: [{ id, toolType, status, totalResults, createdAt }], hasMore, nextCursor? }',
370
+ summary: 'List extraction jobs with pagination and filters',
371
+ },
372
+ {
373
+ category: 'extraction',
374
+ free: false,
375
+ method: 'POST',
376
+ parameters: [
377
+ { description: 'Extraction tool type (reply-extractor, community-explorer, etc.)', in: 'body', name: 'toolType', required: true, type: 'string' },
378
+ { description: 'Target X username', in: 'body', name: 'targetUsername', required: false, type: 'string' },
379
+ { 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' },
381
+ { description: 'Max results to return', in: 'body', name: 'resultsLimit', required: false, type: 'number' },
382
+ ...EXTRACTION_SEARCH_PARAMS,
383
+ ],
384
+ path: '/api/v1/extractions',
385
+ responseShape: '{ id, toolType, status }',
386
+ summary: 'Start a new extraction job',
387
+ },
388
+ {
389
+ category: 'extraction',
390
+ free: false,
391
+ method: 'POST',
392
+ parameters: [
393
+ { description: 'Extraction tool type', in: 'body', name: 'toolType', required: true, type: 'string' },
394
+ { description: 'Target X username', in: 'body', name: 'targetUsername', required: false, type: 'string' },
395
+ { description: 'Target tweet ID', in: 'body', name: 'targetTweetId', required: false, type: 'string' },
396
+ { description: 'Max results to return', in: 'body', name: 'resultsLimit', required: false, type: 'number' },
397
+ ...EXTRACTION_SEARCH_PARAMS,
398
+ ],
399
+ path: '/api/v1/extractions/estimate',
400
+ responseShape: '{ estimatedResults?, usagePercent?, projectedPercent?, allowed?, source? }',
401
+ summary: 'Estimate extraction cost before running',
402
+ },
403
+ {
404
+ category: 'extraction',
405
+ free: false,
406
+ method: 'GET',
407
+ parameters: [
408
+ PARAM_EXTRACTION_ID,
409
+ { description: 'Max results per page', in: 'query', name: 'limit', required: false, type: 'number' },
410
+ { description: DESCRIPTION_PAGINATION_CURSOR, in: 'query', name: 'after', required: false, type: 'string' },
411
+ ],
412
+ path: '/api/v1/extractions/:id',
413
+ responseShape: '{ job: { id, toolType, status, ... }, results: [...], hasMore, nextCursor? }',
414
+ summary: 'Get extraction job details and results',
415
+ },
416
+ {
417
+ category: 'extraction',
418
+ free: false,
419
+ method: 'GET',
420
+ parameters: [PARAM_EXTRACTION_ID, PARAM_EXPORT_FORMAT],
421
+ path: '/api/v1/extractions/:id/export',
422
+ responseShape: 'CSV, XLSX, or Markdown file download',
423
+ summary: 'Export extraction results as CSV, XLSX, or Markdown',
424
+ },
425
+
426
+ // --- Monitoring ---
427
+ {
428
+ category: 'monitoring',
429
+ free: false,
430
+ method: 'GET',
431
+ path: '/api/v1/monitors',
432
+ responseShape: '{ monitors: [{ id, xUsername, eventTypes, isActive, createdAt }], total }',
433
+ summary: 'List all account monitors',
434
+ },
435
+ {
436
+ category: 'monitoring',
437
+ free: false,
438
+ method: 'POST',
439
+ parameters: [
440
+ { description: 'X username to monitor without @', in: 'body', name: 'username', required: true, type: 'string' },
441
+ PARAM_EVENT_TYPES_REQUIRED,
442
+ ],
443
+ path: '/api/v1/monitors',
444
+ responseShape: '{ id, username, eventTypes, createdAt, xUserId }',
445
+ summary: 'Create a new account monitor',
446
+ },
447
+ {
448
+ category: 'monitoring',
449
+ free: false,
450
+ method: 'GET',
451
+ parameters: [PARAM_MONITOR_ID],
452
+ path: '/api/v1/monitors/:id',
453
+ responseShape: '{ id, xUsername, eventTypes, isActive, createdAt }',
454
+ summary: 'Get monitor details by ID',
455
+ },
456
+ {
457
+ category: 'monitoring',
458
+ free: false,
459
+ method: 'PATCH',
460
+ parameters: [
461
+ PARAM_MONITOR_ID,
462
+ { description: 'Set active or paused', in: 'body', name: 'isActive', required: false, type: 'boolean' },
463
+ PARAM_EVENT_TYPES_OPTIONAL,
464
+ ],
465
+ path: '/api/v1/monitors/:id',
466
+ responseShape: '{ id, xUsername, eventTypes, isActive, createdAt }',
467
+ summary: 'Update monitor settings or toggle active state',
468
+ },
469
+ {
470
+ category: 'monitoring',
471
+ free: false,
472
+ method: 'DELETE',
473
+ parameters: [PARAM_MONITOR_ID],
474
+ path: '/api/v1/monitors/:id',
475
+ responseShape: RESPONSE_SUCCESS,
476
+ summary: 'Delete a monitor and stop tracking',
477
+ },
478
+ {
479
+ category: 'monitoring',
480
+ free: false,
481
+ method: 'GET',
482
+ parameters: [
483
+ ...PAGINATION_PARAMS,
484
+ { description: 'Filter by monitor ID', in: 'query', name: 'monitorId', required: false, type: 'string' },
485
+ { description: `Filter by event type: ${DESCRIPTION_EVENT_TYPES}`, in: 'query', name: 'eventType', required: false, type: 'string' },
486
+ ],
487
+ path: '/api/v1/events',
488
+ responseShape: '{ events: [{ id, eventType, xUsername, payload, createdAt }], hasMore, nextCursor? }',
489
+ summary: 'List stream events with filters and pagination',
490
+ },
491
+ {
492
+ category: 'monitoring',
493
+ free: false,
494
+ method: 'GET',
495
+ parameters: [
496
+ { description: 'Event ID', in: 'path', name: 'id', required: true, type: 'string' },
497
+ ],
498
+ path: '/api/v1/events/:id',
499
+ responseShape: '{ id, eventType, xUsername, payload, createdAt, xEventId? }',
500
+ summary: 'Get a single event by ID',
501
+ },
502
+ {
503
+ category: 'monitoring',
504
+ free: false,
505
+ method: 'GET',
506
+ path: '/api/v1/webhooks',
507
+ responseShape: '{ webhooks: [{ id, url, eventTypes, isActive, createdAt }] }',
508
+ summary: 'List all webhook endpoints',
509
+ },
510
+ {
511
+ category: 'monitoring',
512
+ free: false,
513
+ method: 'POST',
514
+ parameters: [
515
+ { description: 'Webhook delivery URL', in: 'body', name: 'url', required: true, type: 'string' },
516
+ PARAM_EVENT_TYPES_REQUIRED,
517
+ ],
518
+ path: '/api/v1/webhooks',
519
+ responseShape: '{ id, url, eventTypes, secret, createdAt }',
520
+ summary: 'Create a new webhook endpoint',
521
+ },
522
+ {
523
+ category: 'monitoring',
524
+ free: false,
525
+ method: 'PATCH',
526
+ parameters: [
527
+ PARAM_WEBHOOK_ID,
528
+ { description: 'Updated delivery URL', in: 'body', name: 'url', required: false, type: 'string' },
529
+ PARAM_EVENT_TYPES_OPTIONAL,
530
+ { description: 'Set active or inactive', in: 'body', name: 'isActive', required: false, type: 'boolean' },
531
+ ],
532
+ path: '/api/v1/webhooks/:id',
533
+ responseShape: '{ id, url, eventTypes, isActive, createdAt }',
534
+ summary: 'Update webhook URL, events, or active state',
535
+ },
536
+ {
537
+ category: 'monitoring',
538
+ free: false,
539
+ method: 'DELETE',
540
+ parameters: [PARAM_WEBHOOK_ID],
541
+ path: '/api/v1/webhooks/:id',
542
+ responseShape: RESPONSE_SUCCESS,
543
+ summary: 'Deactivate a webhook endpoint',
544
+ },
545
+ {
546
+ category: 'monitoring',
547
+ free: false,
548
+ method: 'GET',
549
+ parameters: [PARAM_WEBHOOK_ID],
550
+ path: '/api/v1/webhooks/:id/deliveries',
551
+ responseShape: '{ deliveries: [{ id, status, attempts, statusCode?, createdAt }] }',
552
+ summary: 'List recent deliveries for a webhook',
553
+ },
554
+ {
555
+ category: 'monitoring',
556
+ free: false,
557
+ method: 'POST',
558
+ parameters: [PARAM_WEBHOOK_ID],
559
+ path: '/api/v1/webhooks/:id/test',
560
+ responseShape: '{ success, statusCode, error? }',
561
+ summary: 'Send a test event to a webhook endpoint',
562
+ },
563
+
564
+ // --- Twitter ---
565
+ {
566
+ category: 'twitter',
567
+ free: false,
568
+ method: 'GET',
569
+ parameters: [
570
+ { description: 'Tweet ID to look up', in: 'path', name: 'tweetId', required: true, type: 'string' },
571
+ ],
572
+ path: '/api/v1/x/tweets/:tweetId',
573
+ responseShape: '{ tweet: { id, text, likeCount, retweetCount, replyCount, viewCount, ... }, author? }',
574
+ summary: 'Look up a single tweet with engagement metrics',
575
+ },
576
+ {
577
+ category: 'twitter',
578
+ free: false,
579
+ method: 'GET',
580
+ parameters: [
581
+ { description: 'Search query (X search syntax)', in: 'query', name: 'q', required: true, type: 'string' },
582
+ { description: 'Max tweets to return (default 20, max 200)', in: 'query', name: 'limit', required: false, type: 'number' },
583
+ ],
584
+ path: '/api/v1/x/tweets/search',
585
+ responseShape: '{ tweets: [{ id, text, author?, likeCount?, retweetCount?, media? }], total }',
586
+ summary: 'Search tweets by query with optional limit for pagination',
587
+ },
588
+ {
589
+ category: 'twitter',
590
+ free: false,
591
+ method: 'GET',
592
+ parameters: [
593
+ { description: 'X username to look up', in: 'path', name: 'username', required: true, type: 'string' },
594
+ ],
595
+ path: '/api/v1/x/users/:username',
596
+ responseShape: '{ id, username, name, followers?, following?, verified?, description? }',
597
+ summary: 'Get X user profile by username',
598
+ },
599
+ {
600
+ category: 'twitter',
601
+ free: false,
602
+ method: 'GET',
603
+ parameters: [
604
+ { description: 'Source username', in: 'query', name: 'source', required: true, type: 'string' },
605
+ { description: 'Target username', in: 'query', name: 'target', required: true, type: 'string' },
606
+ ],
607
+ path: '/api/v1/x/followers/check',
608
+ responseShape: '{ isFollowing, isFollowedBy, sourceUsername, targetUsername }',
609
+ summary: 'Check follow relationship between two users',
610
+ },
611
+
612
+ // --- Media ---
613
+ {
614
+ category: 'media',
615
+ free: false,
616
+ method: 'POST',
617
+ parameters: [
618
+ { description: 'Tweet URL or ID (single tweet)', in: 'body', name: 'tweetInput', required: false, type: 'string' },
619
+ { description: 'Array of tweet URLs or IDs (bulk, max 50)', in: 'body', name: 'tweetIds', required: false, type: 'string[]' },
620
+ ],
621
+ path: '/api/v1/x/media/download',
622
+ responseShape: 'Single: { tweetId, galleryUrl, cacheHit }. Bulk: { galleryUrl, totalTweets, totalMedia }',
623
+ summary: 'Download media from tweets. Single tweetInput or bulk tweetIds. Returns gallery URL.',
624
+ },
625
+
626
+ // --- Twitter (Trends) ---
627
+ {
628
+ category: 'twitter',
629
+ free: false,
630
+ method: 'GET',
631
+ parameters: [
632
+ { description: 'WOEID location ID (1 for worldwide)', in: 'query', name: 'woeid', required: false, type: 'number' },
633
+ { description: 'Max number of trends', in: 'query', name: 'count', required: false, type: 'number' },
634
+ ],
635
+ path: '/api/v1/trends',
636
+ responseShape: '{ trends: [{ name, query?, description?, rank? }], total, woeid }',
637
+ summary: 'Get current trending topics on X',
638
+ },
639
+ {
640
+ category: 'trends',
641
+ free: false,
642
+ method: 'GET',
643
+ parameters: [
644
+ { description: 'Source slug (reddit, github, hacker-news, google-trends, wikipedia, startups, polymarket)', in: 'path', name: 'source', required: true, type: 'string' },
645
+ { description: 'Max number of items', in: 'query', name: 'count', required: false, type: 'number' },
646
+ ],
647
+ path: '/api/v1/trending/:source',
648
+ responseShape: '{ items: [{ title, url?, score? }], total, source }',
649
+ summary: 'Get trending items by source',
650
+ },
651
+
652
+ // --- Bot ---
653
+ {
654
+ category: CATEGORY_BOT,
655
+ free: true,
656
+ method: 'POST',
657
+ parameters: [
658
+ { description: 'Platform name (telegram)', in: 'body', name: 'platform', required: true, type: 'string' },
659
+ { description: DESCRIPTION_PLATFORM_USER_ID, in: 'body', name: 'platformUserId', required: true, type: 'string' },
660
+ ],
661
+ path: '/api/v1/bot/platform-links',
662
+ responseShape: '{ id, platform, platformUserId, createdAt }',
663
+ summary: 'Link a platform user to an Xquik account',
664
+ },
665
+ {
666
+ category: CATEGORY_BOT,
667
+ free: true,
668
+ method: 'DELETE',
669
+ parameters: [
670
+ { description: 'Platform name (telegram)', in: 'body', name: 'platform', required: true, type: 'string' },
671
+ { description: DESCRIPTION_PLATFORM_USER_ID, in: 'body', name: 'platformUserId', required: true, type: 'string' },
672
+ ],
673
+ path: '/api/v1/bot/platform-links',
674
+ responseShape: RESPONSE_SUCCESS,
675
+ summary: 'Unlink a platform user from an Xquik account',
676
+ },
677
+ {
678
+ category: CATEGORY_BOT,
679
+ free: true,
680
+ method: 'GET',
681
+ parameters: [
682
+ { description: 'Platform name', in: 'query', name: 'platform', required: true, type: 'string' },
683
+ { description: DESCRIPTION_PLATFORM_USER_ID, in: 'query', name: 'platformUserId', required: true, type: 'string' },
684
+ ],
685
+ path: '/api/v1/bot/platform-links/lookup',
686
+ responseShape: '{ userId }',
687
+ summary: 'Look up an Xquik user by platform identity',
688
+ },
689
+ {
690
+ category: CATEGORY_BOT,
691
+ free: true,
692
+ method: 'POST',
693
+ parameters: [
694
+ { description: DESCRIPTION_PLATFORM_USER_ID, in: 'body', name: 'platformUserId', required: true, type: 'string' },
695
+ { description: 'Input token count', in: 'body', name: 'inputTokens', required: true, type: 'number' },
696
+ { description: 'Output token count', in: 'body', name: 'outputTokens', required: true, type: 'number' },
697
+ ],
698
+ path: '/api/v1/bot/usage',
699
+ responseShape: RESPONSE_SUCCESS,
700
+ summary: 'Track bot token usage',
701
+ },
702
+
703
+ // --- Integrations ---
704
+ {
705
+ category: CATEGORY_INTEGRATIONS,
706
+ free: true,
707
+ method: 'GET',
708
+ path: '/api/v1/integrations',
709
+ responseShape: '{ integrations: [{ id, type, name, config, eventTypes, isActive, ... }] }',
710
+ summary: 'List all integrations (Telegram push notifications)',
711
+ },
712
+ {
713
+ category: CATEGORY_INTEGRATIONS,
714
+ free: true,
715
+ method: 'POST',
716
+ parameters: [
717
+ { description: 'Integration type (telegram)', in: 'body', name: 'type', required: true, type: 'string' },
718
+ { description: 'Display name', in: 'body', name: 'name', required: true, type: 'string' },
719
+ { description: 'Config with chatId', in: 'body', name: 'config', required: true, type: 'object' },
720
+ { description: 'Event types to subscribe to', in: 'body', name: 'eventTypes', required: true, type: 'string[]' },
721
+ ],
722
+ path: '/api/v1/integrations',
723
+ responseShape: '{ id, type, name, config, eventTypes, isActive, ... }',
724
+ summary: 'Create a new integration for push notifications',
725
+ },
726
+ {
727
+ category: CATEGORY_INTEGRATIONS,
728
+ free: true,
729
+ method: 'GET',
730
+ parameters: [PARAM_INTEGRATION_ID],
731
+ path: '/api/v1/integrations/:id',
732
+ responseShape: '{ id, type, name, config, eventTypes, filters, isActive, ... }',
733
+ summary: 'Get integration details',
734
+ },
735
+ {
736
+ category: CATEGORY_INTEGRATIONS,
737
+ free: true,
738
+ method: 'PATCH',
739
+ parameters: [
740
+ PARAM_INTEGRATION_ID,
741
+ { description: 'Display name', in: 'body', name: 'name', required: false, type: 'string' },
742
+ { description: 'Event types', in: 'body', name: 'eventTypes', required: false, type: 'string[]' },
743
+ { description: 'Active status', in: 'body', name: 'isActive', required: false, type: 'boolean' },
744
+ { description: 'Silent notifications', in: 'body', name: 'silentPush', required: false, type: 'boolean' },
745
+ ],
746
+ path: '/api/v1/integrations/:id',
747
+ responseShape: '{ id, type, name, config, eventTypes, isActive, ... }',
748
+ summary: 'Update an integration',
749
+ },
750
+ {
751
+ category: CATEGORY_INTEGRATIONS,
752
+ free: true,
753
+ method: 'DELETE',
754
+ parameters: [PARAM_INTEGRATION_ID],
755
+ path: '/api/v1/integrations/:id',
756
+ responseShape: '{ success: true }',
757
+ summary: 'Delete an integration',
758
+ },
759
+ {
760
+ category: CATEGORY_INTEGRATIONS,
761
+ free: true,
762
+ method: 'GET',
763
+ parameters: [
764
+ PARAM_INTEGRATION_ID,
765
+ { description: 'Max items', in: 'query', name: 'limit', required: false, type: 'number' },
766
+ ],
767
+ path: '/api/v1/integrations/:id/deliveries',
768
+ responseShape: '{ deliveries: [{ id, eventType, status, attempts, createdAt, ... }] }',
769
+ summary: 'List delivery history for an integration',
770
+ },
771
+ {
772
+ category: CATEGORY_INTEGRATIONS,
773
+ free: true,
774
+ method: 'POST',
775
+ parameters: [PARAM_INTEGRATION_ID],
776
+ path: '/api/v1/integrations/:id/test',
777
+ responseShape: '{ success: true }',
778
+ summary: 'Send a test delivery to the integration',
779
+ },
780
+
781
+ // --- X Account Management ---
782
+ {
783
+ category: CATEGORY_X_ACCOUNTS,
784
+ free: true,
785
+ method: 'GET',
786
+ path: '/api/v1/x/accounts',
787
+ responseShape: '{ accounts: [{ id, xUserId, xUsername, status, createdAt }] }',
788
+ summary: 'List connected X accounts',
789
+ },
790
+ {
791
+ category: CATEGORY_X_ACCOUNTS,
792
+ free: true,
793
+ method: 'POST',
794
+ parameters: [
795
+ { description: 'X username', in: 'body', name: 'username', required: true, type: 'string' },
796
+ { description: 'Account email', in: 'body', name: 'email', required: true, type: 'string' },
797
+ { description: 'Account password', in: 'body', name: 'password', required: true, type: 'string' },
798
+ { description: 'TOTP secret for 2FA', in: 'body', name: 'totp_secret', required: false, type: 'string' },
799
+ ],
800
+ path: '/api/v1/x/accounts',
801
+ responseShape: '{ id, xUserId, xUsername, status }',
802
+ summary: 'Connect X account',
803
+ },
804
+ {
805
+ category: CATEGORY_X_ACCOUNTS,
806
+ free: true,
807
+ method: 'GET',
808
+ parameters: [PARAM_X_ACCOUNT_ID],
809
+ path: '/api/v1/x/accounts/:id',
810
+ responseShape: '{ id, xUserId, xUsername, status, cookiesObtainedAt, createdAt }',
811
+ summary: 'Get X account details',
812
+ },
813
+ {
814
+ category: CATEGORY_X_ACCOUNTS,
815
+ free: true,
816
+ method: 'DELETE',
817
+ parameters: [PARAM_X_ACCOUNT_ID],
818
+ path: '/api/v1/x/accounts/:id',
819
+ responseShape: RESPONSE_SUCCESS,
820
+ summary: 'Disconnect X account',
821
+ },
822
+ {
823
+ category: CATEGORY_X_ACCOUNTS,
824
+ free: true,
825
+ method: 'POST',
826
+ parameters: [
827
+ PARAM_X_ACCOUNT_ID,
828
+ { description: 'Account password', in: 'body', name: 'password', required: true, type: 'string' },
829
+ { description: 'TOTP secret for 2FA', in: 'body', name: 'totp_secret', required: false, type: 'string' },
830
+ ],
831
+ path: '/api/v1/x/accounts/:id/reauth',
832
+ responseShape: '{ id, xUsername, status }',
833
+ summary: 'Re-authenticate X account',
834
+ },
835
+
836
+ // --- X Write Actions ---
837
+ {
838
+ category: CATEGORY_X_WRITE,
839
+ free: false,
840
+ method: 'POST',
841
+ parameters: [
842
+ PARAM_X_ACCOUNT,
843
+ { description: 'Tweet text', in: 'body', name: 'text', required: true, type: 'string' },
844
+ { description: 'Tweet ID to reply to', in: 'body', name: 'reply_to_tweet_id', required: false, type: 'string' },
845
+ { description: 'URL to attach', in: 'body', name: 'attachment_url', required: false, type: 'string' },
846
+ { description: 'Community ID to post in', in: 'body', name: 'community_id', required: false, type: 'string' },
847
+ { description: 'Whether this is a long-form note tweet', in: 'body', name: 'is_note_tweet', required: false, type: 'boolean' },
848
+ { description: 'Array of media IDs to attach', in: 'body', name: 'media_ids', required: false, type: 'array' },
849
+ ],
850
+ path: '/api/v1/x/tweets',
851
+ responseShape: '{ tweetId, success: true }',
852
+ summary: 'Create tweet',
853
+ },
854
+ {
855
+ category: CATEGORY_X_WRITE,
856
+ free: false,
857
+ method: 'DELETE',
858
+ parameters: PARAMS_TWEET_ACTION,
859
+ path: '/api/v1/x/tweets/:id',
860
+ responseShape: RESPONSE_SUCCESS,
861
+ summary: 'Delete tweet',
862
+ },
863
+ {
864
+ category: CATEGORY_X_WRITE,
865
+ free: false,
866
+ method: 'POST',
867
+ parameters: PARAMS_TWEET_ACTION,
868
+ path: '/api/v1/x/tweets/:id/like',
869
+ responseShape: RESPONSE_SUCCESS,
870
+ summary: 'Like tweet',
871
+ },
872
+ {
873
+ category: CATEGORY_X_WRITE,
874
+ free: false,
875
+ method: 'DELETE',
876
+ parameters: PARAMS_TWEET_ACTION,
877
+ path: '/api/v1/x/tweets/:id/like',
878
+ responseShape: RESPONSE_SUCCESS,
879
+ summary: 'Unlike tweet',
880
+ },
881
+ {
882
+ category: CATEGORY_X_WRITE,
883
+ free: false,
884
+ method: 'POST',
885
+ parameters: PARAMS_TWEET_ACTION,
886
+ path: '/api/v1/x/tweets/:id/retweet',
887
+ responseShape: RESPONSE_SUCCESS,
888
+ summary: 'Retweet',
889
+ },
890
+ {
891
+ category: CATEGORY_X_WRITE,
892
+ free: false,
893
+ method: 'POST',
894
+ parameters: [PARAM_USER_ID_FOLLOW, PARAM_X_ACCOUNT],
895
+ path: '/api/v1/x/users/:id/follow',
896
+ responseShape: RESPONSE_SUCCESS,
897
+ summary: 'Follow user',
898
+ },
899
+ {
900
+ category: CATEGORY_X_WRITE,
901
+ free: false,
902
+ method: 'DELETE',
903
+ parameters: [PARAM_USER_ID_UNFOLLOW, PARAM_X_ACCOUNT],
904
+ path: '/api/v1/x/users/:id/follow',
905
+ responseShape: RESPONSE_SUCCESS,
906
+ summary: 'Unfollow user',
907
+ },
908
+ {
909
+ category: CATEGORY_X_WRITE,
910
+ free: false,
911
+ method: 'POST',
912
+ parameters: [
913
+ { description: 'Recipient user ID', in: 'path', name: 'userId', required: true, type: 'string' },
914
+ PARAM_X_ACCOUNT,
915
+ { description: 'Message text', in: 'body', name: 'text', required: true, type: 'string' },
916
+ { description: 'Array of media IDs to attach', in: 'body', name: 'media_ids', required: false, type: 'array' },
917
+ { description: 'Message ID to reply to', in: 'body', name: 'reply_to_message_id', required: false, type: 'string' },
918
+ ],
919
+ path: '/api/v1/x/dm/:userId',
920
+ responseShape: '{ messageId, success: true }',
921
+ summary: 'Send DM',
922
+ },
923
+ {
924
+ category: CATEGORY_X_WRITE,
925
+ free: false,
926
+ method: 'POST',
927
+ parameters: [
928
+ PARAM_X_ACCOUNT,
929
+ { description: 'Media file to upload', in: 'body', name: 'file', required: false, type: 'binary' },
930
+ PARAM_MEDIA_URL,
931
+ { description: 'Whether this is a long video', in: 'body', name: 'is_long_video', required: false, type: 'boolean' },
932
+ ],
933
+ path: '/api/v1/x/media',
934
+ responseShape: '{ mediaId, success: true }',
935
+ summary: 'Upload media',
936
+ },
937
+ {
938
+ category: CATEGORY_X_WRITE,
939
+ free: false,
940
+ method: 'PATCH',
941
+ parameters: [
942
+ PARAM_X_ACCOUNT,
943
+ { description: 'Display name', in: 'body', name: 'name', required: false, type: 'string' },
944
+ { description: 'Bio description', in: 'body', name: 'description', required: false, type: 'string' },
945
+ { description: 'Location', in: 'body', name: 'location', required: false, type: 'string' },
946
+ { description: 'Website URL', in: 'body', name: 'url', required: false, type: 'string' },
947
+ ],
948
+ path: '/api/v1/x/profile',
949
+ responseShape: RESPONSE_SUCCESS,
950
+ summary: 'Update profile',
951
+ },
952
+ {
953
+ category: CATEGORY_X_WRITE,
954
+ free: false,
955
+ method: 'PATCH',
956
+ parameters: [
957
+ PARAM_X_ACCOUNT,
958
+ { description: 'Avatar image file', in: 'body', name: 'file', required: false, type: 'binary' },
959
+ PARAM_MEDIA_URL,
960
+ ],
961
+ path: '/api/v1/x/profile/avatar',
962
+ responseShape: RESPONSE_SUCCESS,
963
+ summary: 'Update avatar',
964
+ },
965
+ {
966
+ category: CATEGORY_X_WRITE,
967
+ free: false,
968
+ method: 'PATCH',
969
+ parameters: [
970
+ PARAM_X_ACCOUNT,
971
+ { description: 'Banner image file', in: 'body', name: 'file', required: false, type: 'binary' },
972
+ PARAM_MEDIA_URL,
973
+ ],
974
+ path: '/api/v1/x/profile/banner',
975
+ responseShape: RESPONSE_SUCCESS,
976
+ summary: 'Update banner',
977
+ },
978
+ {
979
+ category: CATEGORY_X_WRITE,
980
+ free: false,
981
+ method: 'POST',
982
+ parameters: [
983
+ PARAM_X_ACCOUNT,
984
+ { description: 'Community name', in: 'body', name: 'name', required: true, type: 'string' },
985
+ { description: 'Community description', in: 'body', name: 'description', required: false, type: 'string' },
986
+ ],
987
+ path: '/api/v1/x/communities',
988
+ responseShape: '{ communityId, success: true }',
989
+ summary: 'Create community',
990
+ },
991
+ {
992
+ category: CATEGORY_X_WRITE,
993
+ free: false,
994
+ method: 'DELETE',
995
+ parameters: [
996
+ PARAM_COMMUNITY_ID,
997
+ PARAM_X_ACCOUNT,
998
+ { description: 'Community name for confirmation', in: 'body', name: 'community_name', required: true, type: 'string' },
999
+ ],
1000
+ path: '/api/v1/x/communities/:id',
1001
+ responseShape: RESPONSE_SUCCESS,
1002
+ summary: 'Delete community',
1003
+ },
1004
+ {
1005
+ category: CATEGORY_X_WRITE,
1006
+ free: false,
1007
+ method: 'POST',
1008
+ parameters: PARAMS_COMMUNITY_ACTION,
1009
+ path: '/api/v1/x/communities/:id/join',
1010
+ responseShape: RESPONSE_COMMUNITY_ACTION,
1011
+ summary: 'Join community',
1012
+ },
1013
+ {
1014
+ category: CATEGORY_X_WRITE,
1015
+ free: false,
1016
+ method: 'DELETE',
1017
+ parameters: PARAMS_COMMUNITY_ACTION,
1018
+ path: '/api/v1/x/communities/:id/join',
1019
+ responseShape: RESPONSE_COMMUNITY_ACTION,
1020
+ summary: 'Leave community',
1021
+ },
1022
+ ] as const;
1023
+
1024
+ export { API_SPEC };