@fundedfast/sdk 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/README.md +352 -0
- package/dist/cjs/client.d.ts +130 -0
- package/dist/cjs/client.d.ts.map +1 -0
- package/dist/cjs/client.js +362 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/index.d.ts +27 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +33 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/resources/admin.d.ts +597 -0
- package/dist/cjs/resources/admin.d.ts.map +1 -0
- package/dist/cjs/resources/admin.js +842 -0
- package/dist/cjs/resources/admin.js.map +1 -0
- package/dist/cjs/resources/public.d.ts +519 -0
- package/dist/cjs/resources/public.d.ts.map +1 -0
- package/dist/cjs/resources/public.js +588 -0
- package/dist/cjs/resources/public.js.map +1 -0
- package/dist/cjs/types.d.ts +651 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +11 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/webhooks.d.ts +104 -0
- package/dist/cjs/webhooks.d.ts.map +1 -0
- package/dist/cjs/webhooks.js +199 -0
- package/dist/cjs/webhooks.js.map +1 -0
- package/dist/esm/client.d.ts +130 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +355 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/index.d.ts +27 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +26 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/resources/admin.d.ts +597 -0
- package/dist/esm/resources/admin.d.ts.map +1 -0
- package/dist/esm/resources/admin.js +816 -0
- package/dist/esm/resources/admin.js.map +1 -0
- package/dist/esm/resources/public.d.ts +519 -0
- package/dist/esm/resources/public.d.ts.map +1 -0
- package/dist/esm/resources/public.js +578 -0
- package/dist/esm/resources/public.js.map +1 -0
- package/dist/esm/types.d.ts +651 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/webhooks.d.ts +104 -0
- package/dist/esm/webhooks.d.ts.map +1 -0
- package/dist/esm/webhooks.js +159 -0
- package/dist/esm/webhooks.js.map +1 -0
- package/package.json +72 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.3.0 (2026-10-06)
|
|
4
|
+
|
|
5
|
+
First release published to npm.
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
- The client refuses to start in a browser with a server key (`key-...`), which
|
|
9
|
+
would publish that key to every visitor. Pass `dangerouslyAllowBrowser: true`
|
|
10
|
+
to override. The public widget key (`pk_...`) is unaffected.
|
|
11
|
+
- The `onRequest` hook receives the API key redacted (`key-***abcd`), so a
|
|
12
|
+
request logger no longer writes the server key to disk.
|
|
13
|
+
- `verifyWebhookSignature` accepts only a 64-character hex string and rejects
|
|
14
|
+
anything else before comparing. The doc examples now `await` it: without
|
|
15
|
+
`await` the result is a Promise, which is always truthy, so every request
|
|
16
|
+
passed.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `downloadExport(date)`, `rewards.list(userId)` and `rewards.history(userId)`
|
|
20
|
+
send the query parameter the API requires; all three answered 400 before.
|
|
21
|
+
- `admin.audit.export()` documents the 90-day window per call.
|
|
22
|
+
- `User-Agent` reports the real SDK version (it said 1.2.0).
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- `ChallengePassedProgress`: `users.getProgress()` can return
|
|
26
|
+
`type: 'challenge_passed'`, with the balance and the continue/refund choices.
|
|
27
|
+
- `WebhookEventType` matches the events the API emits, including
|
|
28
|
+
`bonus.progress` and `mission.progress`.
|
|
29
|
+
|
|
30
|
+
## 1.2.0 (2026-07-18)
|
|
31
|
+
|
|
32
|
+
Alignment release: every SDK method now maps 1:1 to a live API endpoint, and
|
|
33
|
+
all remaining operator-facing endpoints are covered.
|
|
34
|
+
|
|
35
|
+
### Fixed (previously broken methods)
|
|
36
|
+
|
|
37
|
+
These methods called endpoints that do not exist in the API and always failed
|
|
38
|
+
with 404; no working integration can be affected by their change or removal:
|
|
39
|
+
|
|
40
|
+
- `admin.loyalty.setConfig()` now sends `PUT /admin/loyalty/config` (was `POST`, which the API rejects)
|
|
41
|
+
- Removed `admin.challengeTiers.get(id)` - the API has no single-tier GET; use `list()`
|
|
42
|
+
- Removed `admin.rulesets.get(id)` - the API has no single-ruleset GET; use `list()`
|
|
43
|
+
- Removed `admin.reconciliation.forUser(userId)` - use `fast.reconciliation.getForUser(userId)` (the public endpoint)
|
|
44
|
+
- Removed `admin.reconciliation.resolve(userId)` - the API has no flag-resolution endpoint
|
|
45
|
+
- `admin.reconciliation.flags(status?)` now accepts the `open`/`resolved` status filter
|
|
46
|
+
|
|
47
|
+
### Added - operator (public) namespace
|
|
48
|
+
|
|
49
|
+
- `users.continueChallenge(userId)` - continue a passed challenge into the Reward (funded) Account
|
|
50
|
+
- `users.refundChallengeStake(userId)` - exit a passed challenge and refund only the own stake (voids the bonus; sends `confirm: true`)
|
|
51
|
+
- `users.refundFundedStake(userId)` - refund the own stake during the funded phase, if the tier allows it (sends `confirm: true`)
|
|
52
|
+
- `users.getTournaments(userId)` - active/upcoming/completed tournament participation
|
|
53
|
+
- `users.getLoyalty(userId)` - loyalty points, tier, and cashback status
|
|
54
|
+
- `fast.widget.getConfig()` - widget module toggles and branding configuration
|
|
55
|
+
|
|
56
|
+
### Added - admin namespace
|
|
57
|
+
|
|
58
|
+
- `admin.users.get(userId)` - full user lookup (challenges, funded accounts, bets, achievements, rewards)
|
|
59
|
+
- `admin.users.revokeFunded(userId, reason?)` - manually revoke an active funded account
|
|
60
|
+
- `admin.webhooks.retry(id)` - retry a failed webhook delivery
|
|
61
|
+
- `admin.tournaments.update(id, data)` and `admin.tournaments.cancel(id)`
|
|
62
|
+
- `admin.settings.setWidgetLayout({ fundedOnly?, history? })`
|
|
63
|
+
- `admin.analytics.challenges/revenue/users/benchmarks/engagement/funnel/rewards/leaderboard`
|
|
64
|
+
- `admin.audit.export(options?)` (GDPR Art. 20 bulk export) and `admin.audit.retention()`
|
|
65
|
+
- `admin.notificationChannels` - CRUD + test for Slack/Teams/Discord/custom channels
|
|
66
|
+
- `admin.templates` - browse and clone challenge templates from the FAST marketplace
|
|
67
|
+
- `admin.support` - open, read, message, and close support requests
|
|
68
|
+
|
|
69
|
+
## 1.0.0 (2026-06-12)
|
|
70
|
+
|
|
71
|
+
Initial release.
|
|
72
|
+
|
|
73
|
+
### Features
|
|
74
|
+
|
|
75
|
+
- Full typed client for all operator-facing FAST API endpoints
|
|
76
|
+
- Automatic retries with exponential backoff and jitter
|
|
77
|
+
- Rate limit awareness (respects Retry-After header)
|
|
78
|
+
- Request/response hooks for observability (logging, metrics, alerting)
|
|
79
|
+
- Idempotency key support for safe retries on mutations
|
|
80
|
+
- Webhook signature verification (async + sync variants)
|
|
81
|
+
- Webhook event parsing with type helpers
|
|
82
|
+
- SSE token issuance and stream URL builder
|
|
83
|
+
- Custom fetch support (for testing/mocking)
|
|
84
|
+
- Debug mode with console logging
|
|
85
|
+
- Dual ESM/CJS builds
|
|
86
|
+
- Zero dependencies
|
|
87
|
+
- Works in Node.js 18+, Deno, Bun, Cloudflare Workers
|
|
88
|
+
|
|
89
|
+
### API Coverage
|
|
90
|
+
|
|
91
|
+
- `fast.challenges.listTiers()` - List enabled challenge tiers
|
|
92
|
+
- `fast.challenges.getTierTerms(id)` - Get tier terms and disclosures
|
|
93
|
+
- `fast.challenges.purchase(data)` - Purchase a challenge
|
|
94
|
+
- `fast.bets.validate(bet)` - Validate a bet
|
|
95
|
+
- `fast.bets.ingest(bet)` - Ingest a bet event
|
|
96
|
+
- `fast.bets.ingestBatch(bets)` - Batch ingest (up to 100)
|
|
97
|
+
- `fast.users.getProgress(userId)` - Get user progress
|
|
98
|
+
- `fast.users.getRuleset(userId)` - Get active ruleset
|
|
99
|
+
- `fast.users.getHistory(userId)` - Get challenge/funded account history
|
|
100
|
+
- `fast.users.getWithdrawable(userId)` - Get withdrawable amount
|
|
101
|
+
- `fast.users.withdraw(userId, data)` - Process withdrawal
|
|
102
|
+
- `fast.reconciliation.getForUser(userId, options)` - Reconciliation data
|
|
103
|
+
- `fast.events.issueToken(userId)` - Issue SSE token
|
|
104
|
+
- `fast.events.getStreamUrl(token, userId)` - Build SSE URL
|
package/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# @fundedfast/sdk
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for the [FAST](https://fundedfast.eu) platform API.
|
|
4
|
+
|
|
5
|
+
Zero dependencies. Automatic retries. Rate limit aware. Works on Node.js 18+, Deno, Bun and Cloudflare Workers. Dual ESM/CJS build, fully typed.
|
|
6
|
+
|
|
7
|
+
This README is the SDK's API reference. For how the integration fits together (bet flow, webhooks, approvals, the widget, going live), read the integration guide at **[docs.fundedfast.eu](https://docs.fundedfast.eu/integration/quickstart)**.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @fundedfast/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Constructor
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { FastClient } from '@fundedfast/sdk';
|
|
19
|
+
|
|
20
|
+
const fast = new FastClient({
|
|
21
|
+
// Required: your SERVER key (key-...). Never ship it to a browser.
|
|
22
|
+
apiKey: process.env.FAST_API_KEY!,
|
|
23
|
+
|
|
24
|
+
// Optional
|
|
25
|
+
baseUrl: 'https://api.fundedfast.eu/v1', // default
|
|
26
|
+
timeout: 10_000, // per request, ms (default 10s)
|
|
27
|
+
|
|
28
|
+
// Retry (set to false to disable)
|
|
29
|
+
retry: {
|
|
30
|
+
maxRetries: 3, // default 3
|
|
31
|
+
initialDelay: 300, // ms before first retry (default 300)
|
|
32
|
+
maxDelay: 10_000, // cap between retries (default 10s)
|
|
33
|
+
retryableStatuses: [429, 500, 502, 503, 504],
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
headers: { 'X-Request-Source': 'my-service' }, // added to every request
|
|
37
|
+
|
|
38
|
+
// Observability hooks
|
|
39
|
+
onRequest: ({ method, url }) => console.log(`FAST ${method} ${url}`),
|
|
40
|
+
onResponse: ({ status, durationMs, correlationId }) => {
|
|
41
|
+
if (status >= 500) console.error(`FAST ${status} (${durationMs}ms)`, correlationId);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
debug: process.env.NODE_ENV === 'development', // logs requests to console
|
|
45
|
+
// dangerouslyAllowBrowser: true, // the client refuses to run in a browser with a server key
|
|
46
|
+
// fetch: myMockFetch, // custom fetch, e.g. for tests
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Retries back off exponentially with jitter and honour `Retry-After` on 429.
|
|
51
|
+
|
|
52
|
+
**Idempotency.** Every `POST` the SDK sends carries an `X-Idempotency-Key`, generated per call and reused across that call's retries. The money endpoints that require one (`users.withdraw`, `users.refundChallengeStake`, `users.refundFundedStake`) therefore work without you passing anything, and an SDK retry can never pay twice. The key does not survive across separate calls you make: if your process crashes mid-payout, check the account state before calling again. See [Withdrawals](https://docs.fundedfast.eu/integration/withdrawals).
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const tiers = await fast.challenges.listTiers();
|
|
58
|
+
|
|
59
|
+
const challenge = await fast.challenges.purchase({
|
|
60
|
+
userId: 'player-42',
|
|
61
|
+
challengeTierId: tiers[0].id,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const bet = {
|
|
65
|
+
userId: 'player-42',
|
|
66
|
+
externalBetId: 'BET-001',
|
|
67
|
+
stakeAmount: 5.0,
|
|
68
|
+
odds: 2.1,
|
|
69
|
+
status: 'placed' as const,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const { status, reasonCodes } = await fast.bets.validate(bet);
|
|
73
|
+
if (status === 'accepted') {
|
|
74
|
+
await fast.bets.ingest(bet);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
When to validate, what to send on settlement and how sync/async modes differ: [Bet Events](https://docs.fundedfast.eu/integration/bet-events).
|
|
79
|
+
|
|
80
|
+
## Operator namespaces
|
|
81
|
+
|
|
82
|
+
| Namespace | Methods |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `challenges` | `listTiers()`, `getTierTerms(tierId)`, `purchase({ userId, challengeTierId })`, `leaderboard(period?, limit?)` |
|
|
85
|
+
| `bets` | `validate(bet)`, `ingest(bet)`, `ingestBatch(bets)` (max 100) |
|
|
86
|
+
| `users` | `getProgress`, `getRuleset`, `getHistory`, `getWithdrawable`, `withdraw`, `getTimeline`, `getAchievements`, `getRewards`, `claimReward`, `notifyWithdrawal`, `continueChallenge`, `refundChallengeStake`, `refundFundedStake`, `cancelChallenge`, `cancelFundedAccount`, `getTournaments`, `getLoyalty` |
|
|
87
|
+
| `reconciliation` | `getForUser(userId, { from?, to? })` |
|
|
88
|
+
| `events` | `issueToken(userId)`, `getStreamUrl(token, userId)` |
|
|
89
|
+
| `widget` | `getConfig()` |
|
|
90
|
+
| `sandbox` | `reset()`, `resetUser(userId)` (staging tenants only) |
|
|
91
|
+
| `admin.*` | see [Admin API](#admin-api) |
|
|
92
|
+
|
|
93
|
+
### Bets
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Settlement
|
|
97
|
+
await fast.bets.ingest({
|
|
98
|
+
userId: 'player-42',
|
|
99
|
+
externalBetId: 'BET-001',
|
|
100
|
+
stakeAmount: 5.0,
|
|
101
|
+
odds: 2.1,
|
|
102
|
+
status: 'settled-won',
|
|
103
|
+
settlementAmount: 10.5,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Batch (partial success: check result.results for per-bet status)
|
|
107
|
+
const result = await fast.bets.ingestBatch([bet]);
|
|
108
|
+
console.log(`${result.processed}/${result.total} processed, ${result.failed} failed`);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### User progress
|
|
112
|
+
|
|
113
|
+
`getProgress` returns a discriminated union. Narrow on `type`:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const progress = await fast.users.getProgress('player-42');
|
|
117
|
+
|
|
118
|
+
switch (progress.type) {
|
|
119
|
+
case 'challenge':
|
|
120
|
+
console.log(`${progress.currentBalance}/${progress.targetBalance}, ${progress.daysRemaining}d left`);
|
|
121
|
+
break;
|
|
122
|
+
case 'challenge_passed':
|
|
123
|
+
// Target reached; the user must choose. progress.choices is ['continue', 'refund']
|
|
124
|
+
console.log(`Passed. Refundable own stake: ${progress.refundableOwnStake}`);
|
|
125
|
+
break;
|
|
126
|
+
case 'funded_account':
|
|
127
|
+
console.log(`Withdrawable: ${progress.withdrawableProfit}`);
|
|
128
|
+
break;
|
|
129
|
+
case 'none':
|
|
130
|
+
console.log('No active position');
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Field meanings: [User Progress](https://docs.fundedfast.eu/integration/user-progress).
|
|
136
|
+
|
|
137
|
+
### Continue or exit a passed challenge
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Continue into the funded account
|
|
141
|
+
const { fundedAccountId } = await fast.users.continueChallenge('player-42');
|
|
142
|
+
|
|
143
|
+
// Or exit with the own stake (irreversible; the SDK sends confirm: true)
|
|
144
|
+
const { ownStake, bonusAmount } = await fast.users.refundChallengeStake('player-42');
|
|
145
|
+
|
|
146
|
+
// During the funded phase, only on tiers with fundedStakeRefundable
|
|
147
|
+
const refund = await fast.users.refundFundedStake('player-42');
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
FAST records the outcome and emits the webhook; your sportsbook moves the money.
|
|
151
|
+
|
|
152
|
+
### Withdrawals
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const info = await fast.users.getWithdrawable('player-42');
|
|
156
|
+
if (info.activeDaysMet && info.withdrawable > 0) {
|
|
157
|
+
const { userPayout, operatorShare, newBalance } = await fast.users.withdraw('player-42', {
|
|
158
|
+
amount: info.withdrawable,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
How the profit split is deducted and the errors to handle: [Withdrawals](https://docs.fundedfast.eu/integration/withdrawals).
|
|
164
|
+
|
|
165
|
+
### Real-time events (SSE)
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// Backend, with the server key: mint a per-user token (1 hour)
|
|
169
|
+
const { token, expiresAt } = await fast.events.issueToken('player-42');
|
|
170
|
+
|
|
171
|
+
// Hand the token to the browser; it connects with
|
|
172
|
+
const url = fast.events.getStreamUrl(token, 'player-42');
|
|
173
|
+
// new EventSource(url)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The same token is what the widget takes as `data-user-token`. Event names and payloads: [Real-Time Events](https://docs.fundedfast.eu/integration/real-time-events).
|
|
177
|
+
|
|
178
|
+
### Reconciliation
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const data = await fast.reconciliation.getForUser('player-42', {
|
|
182
|
+
from: '2026-06-01T00:00:00Z',
|
|
183
|
+
to: '2026-06-30T23:59:59Z',
|
|
184
|
+
});
|
|
185
|
+
// data.betEvents, data.challenge, data.fundedAccount
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Webhook verification
|
|
189
|
+
|
|
190
|
+
FAST signs each delivery with HMAC-SHA256 over the **raw request body**, using your webhook secret, and sends the hex digest in the `X-Webhook-Signature` header. Verify against the raw bytes: re-serialising parsed JSON changes them and the check fails.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import express from 'express';
|
|
194
|
+
import { verifyWebhookSignature, parseWebhookEvent } from '@fundedfast/sdk/webhooks';
|
|
195
|
+
|
|
196
|
+
const app = express();
|
|
197
|
+
|
|
198
|
+
app.post('/webhooks/fast', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
199
|
+
const rawBody = req.body.toString('utf-8');
|
|
200
|
+
const signature = req.header('x-webhook-signature') ?? '';
|
|
201
|
+
|
|
202
|
+
// Async: uses node:crypto, or Web Crypto on edge runtimes. Timing-safe.
|
|
203
|
+
if (!(await verifyWebhookSignature(rawBody, signature, process.env.FAST_WEBHOOK_SECRET!))) {
|
|
204
|
+
return res.status(401).send('Invalid signature');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const event = parseWebhookEvent(rawBody); // { eventId, eventType, userId, timestamp, data }
|
|
208
|
+
switch (event.eventType) {
|
|
209
|
+
case 'challenge.passed':
|
|
210
|
+
case 'funded_account.payout':
|
|
211
|
+
case 'cooldown.triggered':
|
|
212
|
+
// ...
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
res.status(200).json({ received: true });
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`@fundedfast/sdk/webhooks` also exports `isChallengeEvent`, `isFundedAccountEvent` and `isUserEvent`. `WebhookEventType` lists every event FAST emits. What each event means, retries and deduplication by `eventId`: [Webhook Handling](https://docs.fundedfast.eu/integration/webhook-handling) and [Webhook Events](https://docs.fundedfast.eu/api/webhook-events).
|
|
221
|
+
|
|
222
|
+
## Errors
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { FastApiError, FastTimeoutError, FastNetworkError } from '@fundedfast/sdk';
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await fast.challenges.purchase({ userId: 'x', challengeTierId: 'y' });
|
|
229
|
+
} catch (err) {
|
|
230
|
+
if (err instanceof FastApiError) {
|
|
231
|
+
err.status; // 400, 404, 409, 429, 500...
|
|
232
|
+
err.code; // 'validation_error', 'conflict', 'rate_limited', ...
|
|
233
|
+
err.message; // human-readable
|
|
234
|
+
err.correlationId; // quote this to support
|
|
235
|
+
err.isRetryable; // true for 429 and 5xx
|
|
236
|
+
err.rateLimitReset; // ms from Retry-After, when present
|
|
237
|
+
} else if (err instanceof FastTimeoutError) {
|
|
238
|
+
err.timeoutMs;
|
|
239
|
+
} else if (err instanceof FastNetworkError) {
|
|
240
|
+
err.cause; // the underlying error (DNS, connection refused, ...)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The SDK throws only after its retries are exhausted. Code list: [Error Codes](https://docs.fundedfast.eu/api/error-codes).
|
|
246
|
+
|
|
247
|
+
## Types
|
|
248
|
+
|
|
249
|
+
Every request and response type is exported:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import type {
|
|
253
|
+
Challenge,
|
|
254
|
+
ChallengeTier,
|
|
255
|
+
UserProgress,
|
|
256
|
+
ChallengeProgress,
|
|
257
|
+
ChallengePassedProgress,
|
|
258
|
+
FundedAccountProgress,
|
|
259
|
+
ValidationResult,
|
|
260
|
+
BetEvent,
|
|
261
|
+
WebhookPayload,
|
|
262
|
+
WebhookEventType,
|
|
263
|
+
} from '@fundedfast/sdk';
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// ESM
|
|
268
|
+
import { FastClient } from '@fundedfast/sdk';
|
|
269
|
+
// CommonJS
|
|
270
|
+
const { FastClient } = require('@fundedfast/sdk');
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Admin API
|
|
274
|
+
|
|
275
|
+
The `admin` namespace covers the operator backoffice, with the same server key.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Challenge tiers (no single-tier GET: use list())
|
|
279
|
+
const tiers = await fast.admin.challengeTiers.list();
|
|
280
|
+
await fast.admin.challengeTiers.update(tierId, { enabled: false });
|
|
281
|
+
|
|
282
|
+
// Templates (marketplace bundles you can clone into your tenant)
|
|
283
|
+
const { templates } = await fast.admin.templates.list();
|
|
284
|
+
await fast.admin.templates.clone(templateId, { tierName: 'Starter 100', ownStake: 100 });
|
|
285
|
+
|
|
286
|
+
// Bets
|
|
287
|
+
const bets = await fast.admin.bets.list({ status: 'placed', userId: 'player-42' });
|
|
288
|
+
await fast.admin.bets.settle(betId, 'won');
|
|
289
|
+
await fast.admin.bets.bulkSettle([{ betEventId: betId, outcome: 'won' }]);
|
|
290
|
+
|
|
291
|
+
// Webhooks
|
|
292
|
+
await fast.admin.webhooks.retry(deliveryId);
|
|
293
|
+
const dead = await fast.admin.webhooks.deadLetters();
|
|
294
|
+
await fast.admin.webhooks.replay(deadLetterId);
|
|
295
|
+
await fast.admin.webhooks.rotateSecret();
|
|
296
|
+
const { deadLetters } = await fast.admin.webhooks.hookDeadLetters(); // post-bet hook failures
|
|
297
|
+
await fast.admin.webhooks.replayHook(hookRunId);
|
|
298
|
+
|
|
299
|
+
// Settings
|
|
300
|
+
await fast.admin.settings.setIntegrationMode('async');
|
|
301
|
+
await fast.admin.settings.setWebhookUrl('https://my.backend/webhooks');
|
|
302
|
+
await fast.admin.settings.setCurrency('USD', '$');
|
|
303
|
+
|
|
304
|
+
// Engagement
|
|
305
|
+
await fast.admin.bonuses.activate('player-42', offerId);
|
|
306
|
+
await fast.admin.tournaments.settle(tournamentId);
|
|
307
|
+
await fast.admin.loyalty.adjustPoints('player-42', 500, 'VIP bonus');
|
|
308
|
+
await fast.admin.rewards.grant({ userId: 'player-42', rewardType: 'balance_bonus', rewardValue: 10 });
|
|
309
|
+
await fast.admin.achievements.bulkGrant({ achievementId, userIds: ['player-42'] });
|
|
310
|
+
|
|
311
|
+
// Compliance and support
|
|
312
|
+
await fast.admin.selfExclusion.exclude({ userId: 'player-42', reason: 'request' });
|
|
313
|
+
await fast.admin.users.revokeFunded('player-42', 'manual_operator_revoke');
|
|
314
|
+
const { flags } = await fast.admin.reconciliation.flags('open');
|
|
315
|
+
const auditLog = await fast.admin.audit.list({ type: 'webhooks', limit: 50 });
|
|
316
|
+
await fast.admin.achievements.gdprErase('player-42', 'player-42'); // second arg confirms the id
|
|
317
|
+
const { request } = await fast.admin.support.create({
|
|
318
|
+
subject: 'Webhook retries stuck', body: 'Since 14:00 UTC...', category: 'technical',
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
| Namespace | Methods |
|
|
323
|
+
|---|---|
|
|
324
|
+
| `admin.challengeTiers` | list, create, update, delete |
|
|
325
|
+
| `admin.rulesets` | list, create, update, delete |
|
|
326
|
+
| `admin.bets` | list, settle, bulkSettle, bulkExpire, export |
|
|
327
|
+
| `admin.webhooks` | list, retry, deadLetters, replay, rotateSecret, health, echo, clearEcho, hookDeadLetters, replayHook |
|
|
328
|
+
| `admin.audit` | list, adminLog, export, retention |
|
|
329
|
+
| `admin.settings` | get, getIntegrationMode, setIntegrationMode, setWebhookUrl, setApprovalUrl, setChallengeLimit, setCurrency, setExposureAlert, setWidgetLayout, configExport, downloadExport, testExport, getTheme, setTheme, clearSnapshots |
|
|
330
|
+
| `admin.bonuses` | listOffers, createOffer, updateOffer, deleteOffer, listActive, cancel, grantToUser, stats, getUserBonuses, activate |
|
|
331
|
+
| `admin.missions` | list, create, update, delete, stats, statsForMission, getUserMissions, join |
|
|
332
|
+
| `admin.tournaments` | list, create, update, cancel, settle, entries, invite, invites, stats, listPublic, leaderboard, join |
|
|
333
|
+
| `admin.loyalty` | getConfig, setConfig, getTiers, createTier, updateTier, deleteTier, listUsers, getUser, adjustPoints, getPublicTiers, stats, getUserLoyalty |
|
|
334
|
+
| `admin.analytics` | overview, requestLog, challenges, revenue, users, benchmarks, engagement, funnel, rewards, leaderboard |
|
|
335
|
+
| `admin.achievements` | list, create, update, delete, forUser, grant, bulkGrant, gdprExport, gdprErase |
|
|
336
|
+
| `admin.notifications` | getPreferences, setPreferences |
|
|
337
|
+
| `admin.notificationChannels` | list, create, update, delete, test |
|
|
338
|
+
| `admin.rewards` | list, history, grant, bulkGrant, delete |
|
|
339
|
+
| `admin.rewardRules` | list, create, update, delete |
|
|
340
|
+
| `admin.selfExclusion` | list, get, exclude, lift |
|
|
341
|
+
| `admin.adapters` | list, create, update, delete |
|
|
342
|
+
| `admin.reconciliation` | flags |
|
|
343
|
+
| `admin.users` | get, revokeFunded |
|
|
344
|
+
| `admin.templates` | list, get, clone |
|
|
345
|
+
| `admin.support` | list, get, create, addMessage, close |
|
|
346
|
+
|
|
347
|
+
## Links
|
|
348
|
+
|
|
349
|
+
- Integration guide: https://docs.fundedfast.eu/integration/quickstart
|
|
350
|
+
- Getting access (keys, staging, production): https://docs.fundedfast.eu/integration/getting-access
|
|
351
|
+
- API reference (OpenAPI, try-it-out): https://api.fundedfast.eu/v1/docs
|
|
352
|
+
- Support: support@fundedfast.eu
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fundedfast/sdk - Client
|
|
3
|
+
*
|
|
4
|
+
* Production-grade HTTP client for the FAST platform API.
|
|
5
|
+
* Features: automatic retries with exponential backoff, rate limit awareness,
|
|
6
|
+
* request/response hooks, idempotency support, debug logging, and proper error handling.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { FastClient } from '@fundedfast/sdk';
|
|
11
|
+
*
|
|
12
|
+
* const fast = new FastClient({
|
|
13
|
+
* apiKey: process.env.FAST_API_KEY!,
|
|
14
|
+
* retry: { maxRetries: 3 },
|
|
15
|
+
* onResponse: ({ status, durationMs, correlationId }) => {
|
|
16
|
+
* metrics.histogram('fast.request.duration', durationMs, { status });
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const result = await fast.bets.validate({
|
|
21
|
+
* userId: 'player-42',
|
|
22
|
+
* externalBetId: 'BET-001',
|
|
23
|
+
* stakeAmount: 5.00,
|
|
24
|
+
* odds: 2.10,
|
|
25
|
+
* status: 'placed',
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @module client
|
|
30
|
+
*/
|
|
31
|
+
import type { FastClientConfig, FastError } from './types.js';
|
|
32
|
+
import { ChallengesApi, BetsApi, UsersApi, ReconciliationApi, EventsApi, SandboxApi, WidgetApi } from './resources/public.js';
|
|
33
|
+
import { AdminApi } from './resources/admin.js';
|
|
34
|
+
export declare const SDK_VERSION = "1.3.0";
|
|
35
|
+
/**
|
|
36
|
+
* Error thrown when the FAST API returns a non-2xx response.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* try {
|
|
41
|
+
* await fast.challenges.purchase({ userId: 'x', challengeTierId: 'invalid' });
|
|
42
|
+
* } catch (err) {
|
|
43
|
+
* if (err instanceof FastApiError) {
|
|
44
|
+
* console.log(err.status); // 400
|
|
45
|
+
* console.log(err.code); // 'validation_error'
|
|
46
|
+
* console.log(err.correlationId); // 'abc-123' (for support)
|
|
47
|
+
* console.log(err.isRetryable); // false
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare class FastApiError extends Error {
|
|
53
|
+
readonly status: number;
|
|
54
|
+
readonly code: string;
|
|
55
|
+
readonly correlationId: string;
|
|
56
|
+
readonly isRetryable: boolean;
|
|
57
|
+
readonly rateLimitReset?: number;
|
|
58
|
+
constructor(status: number, body: FastError, rateLimitReset?: number);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Error thrown when a request times out.
|
|
62
|
+
*/
|
|
63
|
+
export declare class FastTimeoutError extends Error {
|
|
64
|
+
readonly timeoutMs: number;
|
|
65
|
+
constructor(timeoutMs: number);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Error thrown when a network error occurs (DNS, connection refused, etc.)
|
|
69
|
+
*/
|
|
70
|
+
export declare class FastNetworkError extends Error {
|
|
71
|
+
readonly cause: Error;
|
|
72
|
+
constructor(cause: Error);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* FAST Platform API client.
|
|
76
|
+
*
|
|
77
|
+
* Provides typed access to all operator-facing endpoints with automatic retry,
|
|
78
|
+
* rate limit handling, and observability hooks.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* const fast = new FastClient({ apiKey: 'key-...' });
|
|
83
|
+
*
|
|
84
|
+
* // Namespaced APIs
|
|
85
|
+
* const tiers = await fast.challenges.listTiers();
|
|
86
|
+
* const result = await fast.bets.validate(betEvent);
|
|
87
|
+
* const progress = await fast.users.getProgress('player-42');
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export declare class FastClient {
|
|
91
|
+
private readonly baseUrl;
|
|
92
|
+
private readonly apiKey;
|
|
93
|
+
private readonly timeout;
|
|
94
|
+
private readonly retryConfig;
|
|
95
|
+
private readonly onRequest?;
|
|
96
|
+
private readonly onResponse?;
|
|
97
|
+
private readonly _fetch;
|
|
98
|
+
private readonly customHeaders;
|
|
99
|
+
private readonly debug;
|
|
100
|
+
/** Challenge purchase and tier management */
|
|
101
|
+
readonly challenges: ChallengesApi;
|
|
102
|
+
/** Bet validation and ingestion */
|
|
103
|
+
readonly bets: BetsApi;
|
|
104
|
+
/** User progress, history, and withdrawals */
|
|
105
|
+
readonly users: UsersApi;
|
|
106
|
+
/** Bet event reconciliation */
|
|
107
|
+
readonly reconciliation: ReconciliationApi;
|
|
108
|
+
/** Real-time event streaming (SSE) */
|
|
109
|
+
readonly events: EventsApi;
|
|
110
|
+
/** Sandbox operations (staging only) */
|
|
111
|
+
readonly sandbox: SandboxApi;
|
|
112
|
+
/** Widget module and branding configuration */
|
|
113
|
+
readonly widget: WidgetApi;
|
|
114
|
+
/** Admin / backoffice operations (tenant management) */
|
|
115
|
+
readonly admin: AdminApi;
|
|
116
|
+
constructor(config: FastClientConfig);
|
|
117
|
+
/**
|
|
118
|
+
* Execute an authenticated request against the FAST API.
|
|
119
|
+
* Handles retries, timeouts, rate limits, and hooks automatically.
|
|
120
|
+
*
|
|
121
|
+
* @internal Use the namespaced APIs (challenges, bets, users, etc.) instead.
|
|
122
|
+
*/
|
|
123
|
+
request<T>(method: string, path: string, options?: {
|
|
124
|
+
body?: unknown;
|
|
125
|
+
query?: Record<string, string | undefined>;
|
|
126
|
+
idempotencyKey?: string;
|
|
127
|
+
}): Promise<T>;
|
|
128
|
+
private calculateDelay;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,SAAS,EAIV,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,OAAO,EACP,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,SAAS,EACV,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAQhD,eAAO,MAAM,WAAW,UAAU,CAAC;AAgCnC;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,aAAa,EAAE,MAAM,CAAC;IACtC,SAAgB,WAAW,EAAE,OAAO,CAAC;IACrC,SAAgB,cAAc,CAAC,EAAE,MAAM,CAAC;gBAE5B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM;CASrE;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAgB,SAAS,EAAE,MAAM,CAAC;gBAEtB,SAAS,EAAE,MAAM;CAK9B;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAgB,KAAK,EAAE,KAAK,CAAC;gBAEjB,KAAK,EAAE,KAAK;CAKzB;AAMD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyB;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAEhC,6CAA6C;IAC7C,SAAgB,UAAU,EAAE,aAAa,CAAC;IAC1C,mCAAmC;IACnC,SAAgB,IAAI,EAAE,OAAO,CAAC;IAC9B,8CAA8C;IAC9C,SAAgB,KAAK,EAAE,QAAQ,CAAC;IAChC,+BAA+B;IAC/B,SAAgB,cAAc,EAAE,iBAAiB,CAAC;IAClD,sCAAsC;IACtC,SAAgB,MAAM,EAAE,SAAS,CAAC;IAClC,wCAAwC;IACxC,SAAgB,OAAO,EAAE,UAAU,CAAC;IACpC,+CAA+C;IAC/C,SAAgB,MAAM,EAAE,SAAS,CAAC;IAClC,wDAAwD;IACxD,SAAgB,KAAK,EAAE,QAAQ,CAAC;gBAEpB,MAAM,EAAE,gBAAgB;IAwCpC;;;;;OAKG;IACG,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;QAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GACA,OAAO,CAAC,CAAC,CAAC;IAgKb,OAAO,CAAC,cAAc;CASvB"}
|