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