@fundedfast/sdk 1.3.0 → 1.4.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.
@@ -1,3 +1,32 @@
1
+ /** Encode one path segment, so an id containing `/`, `?` or `#` cannot change the route. */
2
+ const seg = (value) => encodeURIComponent(value);
3
+ /** Fill the deprecated `reasonCodes` alias from the API's `rejectionReasons`. */
4
+ const withReasonCodes = (b) => ({ ...b, reasonCodes: b.rejectionReasons ?? [] });
5
+ function toGrantBody(data) {
6
+ const { rewardValue, value, ...rest } = data;
7
+ const v = value ?? rewardValue;
8
+ if (v == null)
9
+ throw new TypeError('rewards grant: `value` is required');
10
+ return { ...rest, value: v };
11
+ }
12
+ function toRuleBody(data) {
13
+ const { triggerType, triggerThreshold, ...rest } = data;
14
+ const body = { ...rest };
15
+ if (!rest.criteria && triggerType != null) {
16
+ body.criteria = { type: triggerType, threshold: triggerThreshold ?? 0 };
17
+ }
18
+ return body;
19
+ }
20
+ /** A new rule needs criteria (or the old triggerType), and starts enabled. */
21
+ function toRuleCreateBody(data) {
22
+ const { enabled: _enabled, ...body } = toRuleBody(data);
23
+ if (!body.criteria)
24
+ throw new TypeError('reward rule: `criteria` (or `triggerType`) is required');
25
+ if (body.name == null || body.rewardType == null || body.rewardValue == null) {
26
+ throw new TypeError('reward rule: `name`, `rewardType` and `rewardValue` are required');
27
+ }
28
+ return body;
29
+ }
1
30
  export class AdminApi {
2
31
  challengeTiers;
3
32
  rulesets;
@@ -21,10 +50,12 @@ export class AdminApi {
21
50
  notificationChannels;
22
51
  templates;
23
52
  support;
53
+ payouts;
24
54
  constructor(client) {
25
55
  this.challengeTiers = new AdminChallengeTiersApi(client);
26
56
  this.rulesets = new AdminRulesetsApi(client);
27
57
  this.bets = new AdminBetsApi(client);
58
+ this.payouts = new AdminPayoutsApi(client);
28
59
  this.webhooks = new AdminWebhooksApi(client);
29
60
  this.audit = new AdminAuditApi(client);
30
61
  this.settings = new AdminSettingsApi(client);
@@ -60,11 +91,11 @@ export class AdminChallengeTiersApi {
60
91
  return res.challengeTier;
61
92
  }
62
93
  async update(id, data) {
63
- const res = await this.client.request('PUT', `/admin/challenge-tiers/${id}`, { body: data });
94
+ const res = await this.client.request('PUT', `/admin/challenge-tiers/${seg(id)}`, { body: data });
64
95
  return res.challengeTier;
65
96
  }
66
97
  async delete(id) {
67
- return this.client.request('DELETE', `/admin/challenge-tiers/${id}`);
98
+ return this.client.request('DELETE', `/admin/challenge-tiers/${seg(id)}`);
68
99
  }
69
100
  }
70
101
  export class AdminRulesetsApi {
@@ -81,11 +112,11 @@ export class AdminRulesetsApi {
81
112
  return res.ruleset;
82
113
  }
83
114
  async update(id, data) {
84
- const res = await this.client.request('PUT', `/admin/rulesets/${id}`, { body: data });
115
+ const res = await this.client.request('PUT', `/admin/rulesets/${seg(id)}`, { body: data });
85
116
  return res.ruleset;
86
117
  }
87
118
  async delete(id) {
88
- return this.client.request('DELETE', `/admin/rulesets/${id}`);
119
+ return this.client.request('DELETE', `/admin/rulesets/${seg(id)}`);
89
120
  }
90
121
  }
91
122
  export class AdminBetsApi {
@@ -93,6 +124,7 @@ export class AdminBetsApi {
93
124
  constructor(client) {
94
125
  this.client = client;
95
126
  }
127
+ /** Bet events, newest first (max 200). */
96
128
  async list(options) {
97
129
  const query = {};
98
130
  if (options?.status)
@@ -100,18 +132,29 @@ export class AdminBetsApi {
100
132
  if (options?.userId)
101
133
  query.userId = options.userId;
102
134
  const res = await this.client.request('GET', '/admin/bet-events', { query });
103
- return res.betEvents;
135
+ return res.betEvents.map(withReasonCodes);
104
136
  }
105
137
  async settle(id, outcome) {
106
- const res = await this.client.request('POST', `/admin/bet-events/${id}/settle`, { body: { outcome } });
107
- return res.betEvent;
138
+ const res = await this.client.request('POST', `/admin/bet-events/${seg(id)}/settle`, { body: { outcome } });
139
+ return res.betEvent ? withReasonCodes(res.betEvent) : res.betEvent;
108
140
  }
141
+ /**
142
+ * Settle up to 50 placed bets in one call. Each result says `settled` or
143
+ * `error` (with the reason, e.g. `not_found`, `already_settled`).
144
+ */
109
145
  async bulkSettle(settlements) {
110
- return this.client.request('POST', '/admin/bet-events/bulk-settle', { body: { settlements } });
146
+ const res = await this.client.request('POST', '/admin/bet-events/bulk-settle', { body: { settlements } });
147
+ return { ...res, results: (res.results ?? []).map(r => ({ ...r, success: r.status === 'settled' })) };
111
148
  }
112
149
  async bulkExpire() {
113
150
  return this.client.request('POST', '/admin/challenges/bulk-expire');
114
151
  }
152
+ /**
153
+ * Export bet events as CSV (up to 10,000 rows), header line included.
154
+ *
155
+ * @param options.from / options.to - full ISO 8601 date-times (e.g. `2026-10-01T00:00:00Z`); a bare date is rejected
156
+ * @returns The CSV text
157
+ */
115
158
  async export(options) {
116
159
  const query = {};
117
160
  if (options?.from)
@@ -120,7 +163,42 @@ export class AdminBetsApi {
120
163
  query.to = options.to;
121
164
  if (options?.userId)
122
165
  query.userId = options.userId;
123
- return this.client.request('GET', '/admin/bet-events/export', { query });
166
+ if (options?.status)
167
+ query.status = options.status;
168
+ return this.client.request('GET', '/admin/bet-events/export', { query, responseType: 'text' });
169
+ }
170
+ }
171
+ /** Funded payout requests waiting for the operator, and the decision on them. */
172
+ export class AdminPayoutsApi {
173
+ client;
174
+ constructor(client) {
175
+ this.client = client;
176
+ }
177
+ async list(options) {
178
+ const query = {};
179
+ if (options?.status)
180
+ query.status = options.status;
181
+ if (options?.userId)
182
+ query.userId = options.userId;
183
+ if (options?.limit)
184
+ query.limit = String(options.limit);
185
+ const res = await this.client.request('GET', '/admin/payout-requests', { query });
186
+ return res.requests;
187
+ }
188
+ /**
189
+ * Execute a pending request: the money moves and the cycle closes.
190
+ *
191
+ * @throws {FastApiError} 409 `open_bets` while the account has bets not yet settled; confirm once they have
192
+ * @throws {FastApiError} 409 `payout_amount_changed` when a full-amount request no longer matches what is available
193
+ * (a bet settled since it was made). The request is updated to the new amount; review it and confirm again.
194
+ * @throws {FastApiError} 409 `payout_request_not_pending` if it was already decided
195
+ */
196
+ async confirm(id) {
197
+ return this.client.request('POST', `/admin/payout-requests/${seg(id)}/confirm`, { body: {} });
198
+ }
199
+ /** Reject a pending request. Nothing on the account changes. */
200
+ async reject(id, reason) {
201
+ return this.client.request('POST', `/admin/payout-requests/${seg(id)}/reject`, { body: { reason: reason ?? null } });
124
202
  }
125
203
  }
126
204
  export class AdminWebhooksApi {
@@ -136,12 +214,16 @@ export class AdminWebhooksApi {
136
214
  const res = await this.client.request('GET', '/admin/webhooks/dead-letters');
137
215
  return res.deadLetters;
138
216
  }
217
+ /**
218
+ * Replay a dead-lettered webhook delivery.
219
+ * @throws {FastApiError} 404 `not_found`, 409 `already_replayed`
220
+ */
139
221
  async replay(id) {
140
- return this.client.request('POST', `/admin/webhooks/dead-letters/${id}/replay`);
222
+ return this.client.request('POST', `/admin/webhooks/dead-letters/${seg(id)}/replay`);
141
223
  }
142
224
  /** Retry a failed webhook delivery (resets it to pending and re-dispatches). */
143
225
  async retry(id) {
144
- return this.client.request('POST', `/admin/webhooks/${id}/retry`);
226
+ return this.client.request('POST', `/admin/webhooks/${seg(id)}/retry`);
145
227
  }
146
228
  /**
147
229
  * List dead-letter post-bet hook runs (gamification, bonuses, missions,
@@ -151,12 +233,20 @@ export class AdminWebhooksApi {
151
233
  async hookDeadLetters() {
152
234
  return this.client.request('GET', '/admin/hooks/dead-letters');
153
235
  }
154
- /** Replay a dead-letter post-bet hook run. Double-counting is prevented server-side. */
236
+ /**
237
+ * Replay a dead-letter post-bet hook run. Double-counting is prevented server-side.
238
+ * @throws {FastApiError} 404 `not_found`, 409 `already_replayed`, 502 `hook_failed` when the hook fails again (a 5xx, so the client's retry policy applies first)
239
+ */
155
240
  async replayHook(id) {
156
- return this.client.request('POST', `/admin/hooks/dead-letters/${id}/replay`);
241
+ return this.client.request('POST', `/admin/hooks/dead-letters/${seg(id)}/replay`);
157
242
  }
243
+ /**
244
+ * Rotate the webhook signing secret. The new secret is in `newSecret` and is
245
+ * shown only here; the old one stays valid for `gracePeriodHours`.
246
+ */
158
247
  async rotateSecret() {
159
- return this.client.request('POST', '/admin/webhooks/rotate-secret');
248
+ const res = await this.client.request('POST', '/admin/webhooks/rotate-secret');
249
+ return { ...res, webhookSecret: res.newSecret };
160
250
  }
161
251
  async health() {
162
252
  return this.client.request('GET', '/admin/webhooks/health');
@@ -176,23 +266,44 @@ export class AdminAuditApi {
176
266
  constructor(client) {
177
267
  this.client = client;
178
268
  }
269
+ /**
270
+ * Ledger audit view: bet events (`type: 'bets'`, the default) or webhook
271
+ * deliveries (`type: 'webhooks'`). For the admin action log use {@link adminLog}.
272
+ */
179
273
  async list(options) {
180
274
  const query = {};
181
275
  if (options?.type)
182
276
  query.type = options.type;
277
+ if (options?.userId)
278
+ query.userId = options.userId;
279
+ if (options?.from)
280
+ query.from = options.from;
281
+ if (options?.to)
282
+ query.to = options.to;
183
283
  if (options?.limit)
184
284
  query.limit = String(options.limit);
185
285
  if (options?.offset)
186
286
  query.offset = String(options.offset);
187
287
  return this.client.request('GET', '/audit/log', { query });
188
288
  }
189
- /** Admin audit log (different view from /audit/log) */
289
+ /**
290
+ * Admin action log: who changed what. Filter by `actor` (an admin email, or
291
+ * `api-key`), `action`, `resourceType` and a full ISO 8601 `from`/`to`.
292
+ * `userId` is a deprecated alias of `actor`.
293
+ */
190
294
  async adminLog(options) {
191
295
  const query = {};
192
296
  if (options?.action)
193
297
  query.action = options.action;
194
- if (options?.userId)
195
- query.userId = options.userId;
298
+ const actor = options?.actor ?? options?.userId;
299
+ if (actor)
300
+ query.actor = actor;
301
+ if (options?.resourceType)
302
+ query.resourceType = options.resourceType;
303
+ if (options?.from)
304
+ query.from = options.from;
305
+ if (options?.to)
306
+ query.to = options.to;
196
307
  if (options?.limit)
197
308
  query.limit = String(options.limit);
198
309
  if (options?.offset)
@@ -234,6 +345,7 @@ export class AdminSettingsApi {
234
345
  async getIntegrationMode() {
235
346
  return this.client.request('GET', '/admin/integration-mode');
236
347
  }
348
+ /** Set the webhook URL. Pass null to stop deliveries. */
237
349
  async setWebhookUrl(url) {
238
350
  return this.client.request('PUT', '/admin/settings/webhook', { body: { webhookUrl: url } });
239
351
  }
@@ -248,9 +360,18 @@ export class AdminSettingsApi {
248
360
  async setChallengeLimit(limit) {
249
361
  return this.client.request('PUT', '/admin/settings/challenge-limit', { body: { operatorChallengeLimit: limit } });
250
362
  }
251
- async setCurrency(currency, symbol) {
252
- return this.client.request('PUT', '/admin/settings/currency', { body: { currency, currencySymbol: symbol } });
363
+ /**
364
+ * Set the default currency (ISO 4217) and, optionally, the list of supported
365
+ * currencies. The API has no currency symbol setting; a string passed as the
366
+ * second argument (the old `symbol` parameter) is ignored.
367
+ */
368
+ async setCurrency(defaultCurrency, supportedCurrencies) {
369
+ const body = { defaultCurrency };
370
+ if (Array.isArray(supportedCurrencies))
371
+ body.supportedCurrencies = supportedCurrencies;
372
+ return this.client.request('PUT', '/admin/settings/currency', { body });
253
373
  }
374
+ /** Set the funded exposure alert threshold. Pass null to disable. */
254
375
  async setExposureAlert(threshold) {
255
376
  return this.client.request('PUT', '/admin/exposure-alert', { body: { threshold } });
256
377
  }
@@ -264,8 +385,11 @@ export class AdminSettingsApi {
264
385
  async downloadExport(date) {
265
386
  return this.client.request('GET', '/admin/settings/export/download', { query: { date } });
266
387
  }
267
- async testExport() {
268
- return this.client.request('POST', '/admin/settings/export/test');
388
+ /**
389
+ * Generate the daily export now, for yesterday (UTC) or `date` (YYYY-MM-DD).
390
+ */
391
+ async testExport(date) {
392
+ return this.client.request('POST', '/admin/settings/export/test', { body: date ? { date } : {} });
269
393
  }
270
394
  async getTheme() {
271
395
  const res = await this.client.request('GET', '/admin/theme');
@@ -303,30 +427,35 @@ export class AdminBonusesApi {
303
427
  return res.offer;
304
428
  }
305
429
  async updateOffer(id, data) {
306
- const res = await this.client.request('PUT', `/admin/bonus-offers/${id}`, { body: data });
430
+ const res = await this.client.request('PUT', `/admin/bonus-offers/${seg(id)}`, { body: data });
307
431
  return res.offer;
308
432
  }
433
+ /** Disable an offer. The API keeps the row (sets `enabled: false`) rather than deleting it. */
309
434
  async deleteOffer(id) {
310
- return this.client.request('DELETE', `/admin/bonus-offers/${id}`);
435
+ return this.client.request('DELETE', `/admin/bonus-offers/${seg(id)}`);
311
436
  }
437
+ /** Active user bonuses across the tenant. */
312
438
  async listActive() {
313
439
  const res = await this.client.request('GET', '/admin/bonuses');
314
440
  return res.bonuses;
315
441
  }
316
442
  async cancel(id) {
317
- return this.client.request('POST', `/admin/bonuses/${id}/cancel`);
443
+ return this.client.request('POST', `/admin/bonuses/${seg(id)}/cancel`);
318
444
  }
445
+ /** @param data.depositAmount - Integer cents, like every bonus amount (5000 = 50.00). */
319
446
  async grantToUser(data) {
320
447
  return this.client.request('POST', '/admin/bonuses/grant', { body: data });
321
448
  }
322
449
  async stats() {
323
- return this.client.request('GET', '/admin/bonuses/stats');
450
+ const { correlationId: _c, ...stats } = await this.client.request('GET', '/admin/bonuses/stats');
451
+ return stats;
324
452
  }
325
453
  async getUserBonuses(userId) {
326
- return this.client.request('GET', `/users/${userId}/bonuses`);
454
+ return this.client.request('GET', `/users/${seg(userId)}/bonuses`);
327
455
  }
456
+ /** @param depositAmount - Integer cents (5000 = 50.00); required for deposit_match and reload offers. */
328
457
  async activate(userId, bonusOfferId, depositAmount) {
329
- return this.client.request('POST', `/users/${userId}/bonuses/activate`, { body: { bonusOfferId, depositAmount } });
458
+ return this.client.request('POST', `/users/${seg(userId)}/bonuses/activate`, { body: { bonusOfferId, depositAmount } });
330
459
  }
331
460
  }
332
461
  export class AdminMissionsApi {
@@ -343,23 +472,25 @@ export class AdminMissionsApi {
343
472
  return res.mission;
344
473
  }
345
474
  async update(id, data) {
346
- const res = await this.client.request('PUT', `/admin/missions/${id}`, { body: data });
475
+ const res = await this.client.request('PUT', `/admin/missions/${seg(id)}`, { body: data });
347
476
  return res.mission;
348
477
  }
478
+ /** Disable a mission. The API keeps the row (sets `enabled: false`) rather than deleting it. */
349
479
  async delete(id) {
350
- return this.client.request('DELETE', `/admin/missions/${id}`);
480
+ return this.client.request('DELETE', `/admin/missions/${seg(id)}`);
351
481
  }
352
482
  async stats() {
353
483
  return this.client.request('GET', '/admin/missions/stats');
354
484
  }
355
485
  async statsForMission(id) {
356
- return this.client.request('GET', `/admin/missions/${id}/stats`);
486
+ const res = await this.client.request('GET', `/admin/missions/${seg(id)}/stats`);
487
+ return res.stats;
357
488
  }
358
489
  async getUserMissions(userId) {
359
- return this.client.request('GET', `/users/${userId}/missions`);
490
+ return this.client.request('GET', `/users/${seg(userId)}/missions`);
360
491
  }
361
492
  async join(userId, missionId) {
362
- return this.client.request('POST', `/users/${userId}/missions/${missionId}/join`);
493
+ return this.client.request('POST', `/users/${seg(userId)}/missions/${seg(missionId)}/join`);
363
494
  }
364
495
  }
365
496
  export class AdminTournamentsApi {
@@ -376,24 +507,27 @@ export class AdminTournamentsApi {
376
507
  return res.tournament;
377
508
  }
378
509
  async update(id, data) {
379
- const res = await this.client.request('PUT', `/admin/tournaments/${id}`, { body: data });
510
+ const res = await this.client.request('PUT', `/admin/tournaments/${seg(id)}`, { body: data });
380
511
  return res.tournament;
381
512
  }
382
- /** Cancel an upcoming or active tournament. */
513
+ /**
514
+ * Cancel an upcoming or active tournament. `cancelled` is false when the
515
+ * tournament is unknown, already finished or already cancelled.
516
+ */
383
517
  async cancel(id) {
384
- return this.client.request('POST', `/admin/tournaments/${id}/cancel`);
518
+ return this.client.request('POST', `/admin/tournaments/${seg(id)}/cancel`);
385
519
  }
386
520
  async settle(id) {
387
- return this.client.request('POST', `/admin/tournaments/${id}/settle`);
521
+ return this.client.request('POST', `/admin/tournaments/${seg(id)}/settle`);
388
522
  }
389
523
  async entries(id) {
390
- return this.client.request('GET', `/admin/tournaments/${id}/entries`);
524
+ return this.client.request('GET', `/admin/tournaments/${seg(id)}/entries`);
391
525
  }
392
526
  async invite(id, userIds) {
393
- return this.client.request('POST', `/admin/tournaments/${id}/invite`, { body: { userIds } });
527
+ return this.client.request('POST', `/admin/tournaments/${seg(id)}/invite`, { body: { userIds } });
394
528
  }
395
529
  async invites(id) {
396
- return this.client.request('GET', `/admin/tournaments/${id}/invites`);
530
+ return this.client.request('GET', `/admin/tournaments/${seg(id)}/invites`);
397
531
  }
398
532
  async stats() {
399
533
  return this.client.request('GET', '/admin/tournaments/stats');
@@ -403,10 +537,10 @@ export class AdminTournamentsApi {
403
537
  return res.tournaments;
404
538
  }
405
539
  async leaderboard(tournamentId, limit = 10) {
406
- return this.client.request('GET', `/tournaments/${tournamentId}/leaderboard`, { query: { limit: String(limit) } });
540
+ return this.client.request('GET', `/tournaments/${seg(tournamentId)}/leaderboard`, { query: { limit: String(limit) } });
407
541
  }
408
542
  async join(userId, tournamentId) {
409
- return this.client.request('POST', `/users/${userId}/tournaments/${tournamentId}/join`);
543
+ return this.client.request('POST', `/users/${seg(userId)}/tournaments/${seg(tournamentId)}/join`);
410
544
  }
411
545
  }
412
546
  export class AdminLoyaltyApi {
@@ -431,26 +565,44 @@ export class AdminLoyaltyApi {
431
565
  return res.tier;
432
566
  }
433
567
  async updateTier(id, data) {
434
- const res = await this.client.request('PUT', `/admin/loyalty/tiers/${id}`, { body: data });
568
+ const res = await this.client.request('PUT', `/admin/loyalty/tiers/${seg(id)}`, { body: data });
435
569
  return res.tier;
436
570
  }
437
571
  async deleteTier(id) {
438
- return this.client.request('DELETE', `/admin/loyalty/tiers/${id}`);
572
+ return this.client.request('DELETE', `/admin/loyalty/tiers/${seg(id)}`);
439
573
  }
574
+ /**
575
+ * Users' loyalty state, most points first. Filter by `tier`, the tier
576
+ * **name**. `tierId` is still accepted: the SDK looks the name up first.
577
+ */
440
578
  async listUsers(options) {
441
579
  const query = {};
442
- if (options?.tierId)
443
- query.tierId = options.tierId;
580
+ let tier = options?.tier;
581
+ if (!tier && options?.tierId) {
582
+ const match = (await this.getTiers()).find(t => t.id === options.tierId);
583
+ // An unknown id must not widen the result to every user.
584
+ tier = match ? match.name : options.tierId;
585
+ }
586
+ if (tier)
587
+ query.tier = tier;
444
588
  if (options?.limit)
445
589
  query.limit = String(options.limit);
590
+ if (options?.offset)
591
+ query.offset = String(options.offset);
446
592
  return this.client.request('GET', '/admin/loyalty/users', { query });
447
593
  }
594
+ /** One user's loyalty state and recent point history. `loyalty` is null when the user has none yet. */
448
595
  async getUser(userId) {
449
- const res = await this.client.request('GET', `/admin/loyalty/users/${userId}`);
450
- return res.user;
596
+ const { loyalty, history } = await this.client.request('GET', `/admin/loyalty/users/${seg(userId)}`);
597
+ return { loyalty, history };
451
598
  }
599
+ /**
600
+ * Add (positive) or remove (negative) points. The API records a reason with
601
+ * every adjustment; when none is given the SDK sends `'Manual adjustment'`.
602
+ */
452
603
  async adjustPoints(userId, points, reason) {
453
- return this.client.request('POST', `/admin/loyalty/users/${userId}/adjust`, { body: { points, reason } });
604
+ const { newTotal } = await this.client.request('POST', `/admin/loyalty/users/${seg(userId)}/adjust`, { body: { points, reason: reason || 'Manual adjustment' } });
605
+ return { newTotal };
454
606
  }
455
607
  async getPublicTiers() {
456
608
  const res = await this.client.request('GET', '/loyalty/tiers');
@@ -461,7 +613,7 @@ export class AdminLoyaltyApi {
461
613
  return res.stats;
462
614
  }
463
615
  async getUserLoyalty(userId) {
464
- const res = await this.client.request('GET', `/users/${userId}/loyalty`);
616
+ const res = await this.client.request('GET', `/users/${seg(userId)}/loyalty`);
465
617
  return res.loyalty;
466
618
  }
467
619
  }
@@ -470,26 +622,42 @@ export class AdminAnalyticsApi {
470
622
  constructor(client) {
471
623
  this.client = client;
472
624
  }
473
- async overview() {
474
- return this.client.request('GET', '/admin/overview');
625
+ /** Dashboard overview for a month (default: the current one). */
626
+ async overview(options) {
627
+ const query = {};
628
+ if (options?.month)
629
+ query.month = String(options.month);
630
+ if (options?.year)
631
+ query.year = String(options.year);
632
+ return this.client.request('GET', '/admin/overview', { query });
475
633
  }
634
+ /**
635
+ * Recent API requests for the tenant, newest first (max 200). Filter by
636
+ * `path` prefix, `status` (e.g. `500`, `4xx`) or `correlationId`.
637
+ * The API does not paginate; `offset` is ignored.
638
+ */
476
639
  async requestLog(options) {
477
640
  const query = {};
641
+ if (options?.path)
642
+ query.path = options.path;
643
+ if (options?.status)
644
+ query.status = options.status;
645
+ if (options?.correlationId)
646
+ query.correlationId = options.correlationId;
478
647
  if (options?.limit)
479
648
  query.limit = String(options.limit);
480
- if (options?.offset)
481
- query.offset = String(options.offset);
482
- return this.client.request('GET', '/admin/request-log', { query });
649
+ const { requests } = await this.client.request('GET', '/admin/request-log', { query });
650
+ return { requests };
483
651
  }
484
- /** Challenge analytics (pass/fail rates, volumes) with optional ISO 8601 date range. */
652
+ /** Challenge analytics (pass/fail rates, volumes). `from`/`to` are full ISO 8601 date-times; a bare date is rejected. */
485
653
  async challenges(options) {
486
654
  return this.client.request('GET', '/analytics/challenges', { query: { from: options?.from, to: options?.to } });
487
655
  }
488
- /** Revenue analytics with optional ISO 8601 date range. */
656
+ /** Revenue analytics. `from`/`to` are full ISO 8601 date-times; a bare date is rejected. */
489
657
  async revenue(options) {
490
658
  return this.client.request('GET', '/analytics/revenue', { query: { from: options?.from, to: options?.to } });
491
659
  }
492
- /** User cohort analytics with optional ISO 8601 date range. */
660
+ /** User cohort analytics. `from`/`to` are full ISO 8601 date-times; a bare date is rejected. */
493
661
  async users(options) {
494
662
  return this.client.request('GET', '/analytics/users', { query: { from: options?.from, to: options?.to } });
495
663
  }
@@ -536,27 +704,29 @@ export class AdminAchievementsApi {
536
704
  return res.achievement;
537
705
  }
538
706
  async update(id, data) {
539
- const res = await this.client.request('PUT', `/admin/achievements/${id}`, { body: data });
707
+ const res = await this.client.request('PUT', `/admin/achievements/${seg(id)}`, { body: data });
540
708
  return res.achievement;
541
709
  }
542
710
  async delete(id) {
543
- return this.client.request('DELETE', `/admin/achievements/${id}`);
711
+ return this.client.request('DELETE', `/admin/achievements/${seg(id)}`);
544
712
  }
545
713
  async forUser(userId) {
546
- return this.client.request('GET', `/admin/users/${userId}/achievements`);
714
+ return this.client.request('GET', `/admin/users/${seg(userId)}/achievements`);
547
715
  }
716
+ /** @throws {FastApiError} 409 `already_earned` if the user has it */
548
717
  async grant(userId, achievementId) {
549
- const res = await this.client.request('POST', `/admin/users/${userId}/achievements`, { body: { achievementId } });
718
+ const res = await this.client.request('POST', `/admin/users/${seg(userId)}/achievements`, { body: { achievementId } });
550
719
  return res.userAchievement;
551
720
  }
721
+ /** Grant one achievement to up to 500 users. Users who already have it are counted in `skipped`. */
552
722
  async bulkGrant(data) {
553
723
  return this.client.request('POST', '/admin/achievements/bulk-grant', { body: data });
554
724
  }
555
725
  async gdprExport(userId) {
556
- return this.client.request('GET', `/admin/users/${userId}/gdpr-export`);
726
+ return this.client.request('GET', `/admin/users/${seg(userId)}/gdpr-export`);
557
727
  }
558
728
  async gdprErase(userId, confirmUserId) {
559
- return this.client.request('POST', `/admin/users/${userId}/gdpr-erase`, { body: { confirmUserId } });
729
+ return this.client.request('POST', `/admin/users/${seg(userId)}/gdpr-erase`, { body: { confirmUserId } });
560
730
  }
561
731
  }
562
732
  /**
@@ -596,19 +766,33 @@ export class AdminRewardsApi {
596
766
  const query = { userId };
597
767
  if (options?.limit)
598
768
  query.limit = String(options.limit);
599
- return this.client.request('GET', '/admin/rewards/history', { query });
769
+ const { history } = await this.client.request('GET', '/admin/rewards/history', { query });
770
+ return { history };
600
771
  }
601
- /** Manually grant a reward to a user. */
772
+ /**
773
+ * Manually grant a reward to a user.
774
+ *
775
+ * `value` is in EUR for money types, otherwise percent or days.
776
+ * `rewardValue` is accepted as a deprecated alias of `value`.
777
+ *
778
+ * @returns The id of the new pending reward
779
+ */
602
780
  async grant(data) {
603
- return this.client.request('POST', '/admin/rewards/grant', { body: data });
781
+ const { rewardId } = await this.client.request('POST', '/admin/rewards/grant', { body: toGrantBody(data) });
782
+ return { rewardId };
604
783
  }
605
- /** Bulk grant a reward to multiple users. */
784
+ /** Grant the same reward to up to 500 users. `rewardValue` is a deprecated alias of `value`. */
606
785
  async bulkGrant(data) {
607
- return this.client.request('POST', '/admin/rewards/bulk-grant', { body: data });
786
+ const { granted, failed } = await this.client.request('POST', '/admin/rewards/bulk-grant', { body: toGrantBody(data) });
787
+ return { granted, failed };
608
788
  }
609
- /** Delete (revoke) a pending reward. */
789
+ /**
790
+ * Revoke a pending (unconsumed) reward.
791
+ * @throws {FastApiError} 404 if it does not exist or was already consumed
792
+ */
610
793
  async delete(id) {
611
- return this.client.request('DELETE', `/admin/rewards/${id}`);
794
+ const { revoked } = await this.client.request('DELETE', `/admin/rewards/${seg(id)}`);
795
+ return { revoked, deleted: revoked };
612
796
  }
613
797
  }
614
798
  export class AdminRewardRulesApi {
@@ -622,18 +806,22 @@ export class AdminRewardRulesApi {
622
806
  return res.rules;
623
807
  }
624
808
  /** Create a new reward rule. */
809
+ /**
810
+ * Create a reward rule. `name`, `criteria`, `rewardType` and `rewardValue`
811
+ * are required. The deprecated `triggerType`/`triggerThreshold` are mapped to `criteria`.
812
+ */
625
813
  async create(data) {
626
- const res = await this.client.request('POST', '/admin/reward-rules', { body: data });
814
+ const res = await this.client.request('POST', '/admin/reward-rules', { body: toRuleCreateBody(data) });
627
815
  return res.rule;
628
816
  }
629
817
  /** Update an existing reward rule. */
630
818
  async update(id, data) {
631
- const res = await this.client.request('PUT', `/admin/reward-rules/${id}`, { body: data });
819
+ const res = await this.client.request('PUT', `/admin/reward-rules/${seg(id)}`, { body: toRuleBody(data) });
632
820
  return res.rule;
633
821
  }
634
822
  /** Delete a reward rule. */
635
823
  async delete(id) {
636
- return this.client.request('DELETE', `/admin/reward-rules/${id}`);
824
+ return this.client.request('DELETE', `/admin/reward-rules/${seg(id)}`);
637
825
  }
638
826
  }
639
827
  export class AdminSelfExclusionApi {
@@ -641,21 +829,46 @@ export class AdminSelfExclusionApi {
641
829
  constructor(client) {
642
830
  this.client = client;
643
831
  }
644
- /** List all currently excluded users. */
645
- async list() {
646
- return this.client.request('GET', '/admin/self-exclusion');
832
+ /** List currently excluded users (paginated: limit default 100, max 500). */
833
+ async list(options) {
834
+ const query = {};
835
+ if (options?.limit != null)
836
+ query.limit = String(options.limit);
837
+ if (options?.offset != null)
838
+ query.offset = String(options.offset);
839
+ const { entries, total, limit, offset } = await this.client.request('GET', '/admin/self-exclusion', { query });
840
+ return { entries, total, limit, offset, exclusions: entries };
647
841
  }
648
- /** Get exclusion status for a specific user. Returns 404 if not excluded. */
842
+ /**
843
+ * Get the exclusion for a user.
844
+ * @throws {FastApiError} 404 if the user is not excluded
845
+ */
649
846
  async get(userId) {
650
- return this.client.request('GET', `/admin/self-exclusion/${userId}`);
847
+ const res = await this.client.request('GET', `/admin/self-exclusion/${seg(userId)}`);
848
+ return res.entry;
651
849
  }
652
- /** Exclude a user from purchasing challenges. Upsert: re-excluding updates the record. */
850
+ /**
851
+ * Exclude a user from purchasing challenges. Upsert: re-excluding updates the record.
852
+ *
853
+ * Give `expiresAt` (ISO 8601) for a temporary exclusion; omit it (or pass
854
+ * null) for a permanent one. `durationDays` is converted to `expiresAt`
855
+ * client-side (now + days).
856
+ */
653
857
  async exclude(data) {
654
- return this.client.request('POST', '/admin/self-exclusion', { body: data });
858
+ const { durationDays, ...body } = data;
859
+ if (body.expiresAt === undefined && durationDays != null) {
860
+ body.expiresAt = new Date(Date.now() + durationDays * 86_400_000).toISOString();
861
+ }
862
+ const res = await this.client.request('POST', '/admin/self-exclusion', { body });
863
+ return res.entry;
655
864
  }
656
- /** Lift (remove) self-exclusion for a user. */
865
+ /**
866
+ * Lift the exclusion for a user (the record is removed).
867
+ * @throws {FastApiError} 404 if the user is not excluded
868
+ */
657
869
  async lift(userId) {
658
- return this.client.request('DELETE', `/admin/self-exclusion/${userId}`);
870
+ const res = await this.client.request('DELETE', `/admin/self-exclusion/${seg(userId)}`);
871
+ return { reinstated: res.reinstated, userId: res.userId, lifted: res.reinstated };
659
872
  }
660
873
  }
661
874
  export class AdminAdaptersApi {
@@ -675,12 +888,12 @@ export class AdminAdaptersApi {
675
888
  }
676
889
  /** Update an adapter mapping. */
677
890
  async update(id, data) {
678
- const res = await this.client.request('PUT', `/admin/adapters/${id}`, { body: data });
891
+ const res = await this.client.request('PUT', `/admin/adapters/${seg(id)}`, { body: data });
679
892
  return res.adapter;
680
893
  }
681
894
  /** Delete an adapter mapping. */
682
895
  async delete(id) {
683
- return this.client.request('DELETE', `/admin/adapters/${id}`);
896
+ return this.client.request('DELETE', `/admin/adapters/${seg(id)}`);
684
897
  }
685
898
  }
686
899
  export class AdminReconciliationApi {
@@ -713,7 +926,7 @@ export class AdminUsersApi {
713
926
  * bet events, achievements, and pending/applied rewards.
714
927
  */
715
928
  async get(userId) {
716
- return this.client.request('GET', `/admin/users/${userId}`);
929
+ return this.client.request('GET', `/admin/users/${seg(userId)}`);
717
930
  }
718
931
  /**
719
932
  * Manually revoke the active funded account for a user.
@@ -726,7 +939,7 @@ export class AdminUsersApi {
726
939
  * @throws {FastApiError} 404 if the user has no active funded account
727
940
  */
728
941
  async revokeFunded(userId, reason) {
729
- return this.client.request('POST', `/admin/users/${userId}/revoke-funded`, { body: reason ? { reason } : {} });
942
+ return this.client.request('POST', `/admin/users/${seg(userId)}/revoke-funded`, { body: reason ? { reason } : {} });
730
943
  }
731
944
  }
732
945
  /**
@@ -748,14 +961,14 @@ export class AdminNotificationChannelsApi {
748
961
  return this.client.request('POST', '/admin/notification-channels', { body: data });
749
962
  }
750
963
  async update(id, data) {
751
- return this.client.request('PUT', `/admin/notification-channels/${id}`, { body: data });
964
+ return this.client.request('PUT', `/admin/notification-channels/${seg(id)}`, { body: data });
752
965
  }
753
966
  async delete(id) {
754
- return this.client.request('DELETE', `/admin/notification-channels/${id}`);
967
+ return this.client.request('DELETE', `/admin/notification-channels/${seg(id)}`);
755
968
  }
756
969
  /** Send a test notification to the channel. */
757
970
  async test(id) {
758
- return this.client.request('POST', `/admin/notification-channels/${id}/test`);
971
+ return this.client.request('POST', `/admin/notification-channels/${seg(id)}/test`);
759
972
  }
760
973
  }
761
974
  /**
@@ -771,14 +984,15 @@ export class AdminTemplatesApi {
771
984
  return this.client.request('GET', '/admin/templates');
772
985
  }
773
986
  async get(id) {
774
- return this.client.request('GET', `/admin/templates/${id}`);
987
+ const res = await this.client.request('GET', `/admin/templates/${seg(id)}`);
988
+ return res.template;
775
989
  }
776
990
  /**
777
991
  * Clone a template into your tenant. Creates a new ruleset and challenge
778
992
  * tier; you can override the tier name, own stake, and duration.
779
993
  */
780
994
  async clone(id, overrides) {
781
- return this.client.request('POST', `/admin/templates/${id}/clone`, { body: overrides ?? {} });
995
+ return this.client.request('POST', `/admin/templates/${seg(id)}/clone`, { body: overrides ?? {} });
782
996
  }
783
997
  }
784
998
  /**
@@ -789,28 +1003,40 @@ export class AdminSupportApi {
789
1003
  constructor(client) {
790
1004
  this.client = client;
791
1005
  }
1006
+ /** Support requests for the tenant. The API does not paginate; `limit` is ignored. */
792
1007
  async list(options) {
793
1008
  const query = {};
794
1009
  if (options?.status)
795
1010
  query.status = options.status;
796
- if (options?.limit)
797
- query.limit = String(options.limit);
798
1011
  return this.client.request('GET', '/admin/support/requests', { query });
799
1012
  }
1013
+ /** A support request with its messages. */
800
1014
  async get(id) {
801
- return this.client.request('GET', `/admin/support/requests/${id}`);
1015
+ return this.client.request('GET', `/admin/support/requests/${seg(id)}`);
802
1016
  }
803
- /** Open a new support request. Category defaults to 'other'. */
1017
+ /**
1018
+ * Open a new support request. Category defaults to 'other'.
1019
+ *
1020
+ * @deprecated Needs an admin session: the API records which admin wrote it
1021
+ * and answers 400 `unauthorized` to a server API key. It works only if the
1022
+ * client sends an admin JWT, e.g. `new FastClient({ apiKey, headers: { Authorization: 'Bearer <admin JWT>' } })`.
1023
+ * Open requests from the backoffice instead.
1024
+ */
804
1025
  async create(data) {
805
1026
  return this.client.request('POST', '/admin/support/requests', { body: data });
806
1027
  }
807
- /** Add a message to an existing support request. */
1028
+ /**
1029
+ * Add a message to an existing support request.
1030
+ *
1031
+ * @deprecated Needs an admin session, like {@link create}: a server API key
1032
+ * gets 400 `unauthorized`. Reply from the backoffice instead.
1033
+ */
808
1034
  async addMessage(id, body) {
809
- return this.client.request('POST', `/admin/support/requests/${id}/messages`, { body: { body } });
1035
+ return this.client.request('POST', `/admin/support/requests/${seg(id)}/messages`, { body: { body } });
810
1036
  }
811
1037
  /** Close a support request. */
812
1038
  async close(id) {
813
- return this.client.request('POST', `/admin/support/requests/${id}/close`);
1039
+ return this.client.request('POST', `/admin/support/requests/${seg(id)}/close`);
814
1040
  }
815
1041
  }
816
1042
  //# sourceMappingURL=admin.js.map