@globalscoutme/api-client 1.1.29 → 1.2.1

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/README.md CHANGED
@@ -1,303 +1,334 @@
1
- # @globalscoutme/api-client
2
-
3
- Auto-generated TypeScript HTTP client for the GlobalScoutMe API, built with [`@hey-api/openapi-ts`](https://heyapi.dev/) and axios.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install @globalscoutme/api-client
9
- ```
10
-
11
- ## Setup
12
-
13
- Configure the client once at app startup with your API base URL and auth token:
14
-
15
- ```ts
16
- import { client } from '@globalscoutme/api-client';
17
-
18
- client.setConfig({
19
- baseURL: 'https://your-api-url.com',
20
- headers: {
21
- Authorization: `Bearer ${yourSupabaseAccessToken}`,
22
- },
23
- });
24
- ```
25
-
26
- ## Usage
27
-
28
- All endpoints are grouped by controller and fully typed:
29
-
30
- ```ts
31
- import {
32
- Auth,
33
- Dashboard,
34
- Players,
35
- Profile,
36
- Documents,
37
- Videos,
38
- Achievements,
39
- Body,
40
- Index,
41
- Kyc,
42
- Offers,
43
- Organizations,
44
- Clubs,
45
- Challenges,
46
- Training,
47
- Invitations,
48
- Storage,
49
- Subscriptions,
50
- } from '@globalscoutme/api-client';
51
-
52
- // Auth
53
- const authMe = await Auth.getAuthMe();
54
- // Returns { id: string, userType: string | null, orgRole: 'admin' | 'user' | null }
55
- // orgRole is the caller's organization_members role (club_admin/scouter only); null for player/coach
56
-
57
- // Players
58
- const player = await Players.getMe();
59
- await Players.patchMe({ body: { fullName: 'John Doe' } });
60
- await Players.deleteMe();
61
- const dashboard = await Players.getMyDashboard();
62
- await Players.heartbeat({ body: { timezone: 'Europe/Bucharest' } });
63
- const results = await Players.searchPlayers({
64
- body: {
65
- search: 'John',
66
- page: 1,
67
- pageSize: 20,
68
- sortBy: 'age_asc',
69
- nationalities: ['EG', 'MA'],
70
- positions: ['CM', 'CB'],
71
- baseFoot: 'right',
72
- isFreeAgent: true,
73
- hasVideo: true,
74
- heightOperator: 'gte',
75
- heightCm: 180,
76
- },
77
- });
78
- const playerProfile = await Players.getPlayerById({
79
- path: { id: 'player-uuid' },
80
- });
81
- const playerMeasurements = await Players.getPlayerMeasurementProfile({
82
- path: { id: 'player-uuid' },
83
- });
84
- // Returns { status, body, cmj, session, updatedAt } — accessible to all roles (no player-only restriction)
85
- // status: 'missing' | 'partial' | 'ready' | 'needs_update'
86
- const playerVideoMetrics = await Players.getPlayerVideoMetrics({
87
- path: { id: 'player-uuid' },
88
- });
89
- // Returns { id, createdAt, videoDurationS, physicalMetrics, trackingQuality, trackedFrames, totalFrames }
90
- // physicalMetrics: { totalDistance, sprintDistance, maxSpeed, averageSpeed, highIntensityRuns, sprints }
91
- // Speed/distance values are strings with units already included (e.g. "31.0 km/h", "21.4 m")
92
- // trackingQuality: confidence label from the analysis service (e.g. "High Confidence") — null if unavailable
93
- // Returns 404 if the player has no completed video analysis yet
94
- const playerReport = await Players.getPlayerReport({
95
- path: { id: 'player-uuid' },
96
- });
97
- // Returns { reportReferenceId, generatedAt, scouterView: { technicalPillar, physicalPillar, tacticalPillar, mentalPillar } }
98
- // Returns 404 if the player has no completed AI report yet
99
-
100
- // Profile
101
- const profile = await Profile.getProfileMe();
102
- await Profile.updateProfileMe({ body: { dominantFoot: 'Right' } });
103
-
104
- // Documents
105
- const docs = await Documents.getMyDocuments();
106
- await Documents.createDocument({ body: { type: 'license' } });
107
-
108
- // Videos
109
- const videos = await Videos.getMyVideos();
110
- await Videos.createVideo({
111
- body: { type: 'cmj', storagePath: 'path/to/video' },
112
- });
113
- const playUrl = await Videos.getVideoPlayUrl({ path: { id: 'video-uuid' } });
114
- await Videos.deleteVideo({ path: { id: 'video-uuid' } });
115
-
116
- // Achievements
117
- const catalog = await Achievements.getAchievementsCatalog();
118
- const myAchievements = await Achievements.getMyAchievements();
119
-
120
- // Body measurements
121
- const measurement = await Body.getMyMeasurements();
122
- await Body.createMeasurement({ body: { heightCm: 180 } });
123
-
124
- // Index (lookup tables)
125
- const positions = await Index.getIndexItems({
126
- path: { tableName: 'positions' },
127
- });
128
- const countries = await Index.getCountries();
129
- // Returns [{ code: 'BR', name: 'Brazil', nationalityName: 'Brazilian', flagEmoji: '🇧🇷' }, ...]
130
-
131
- // Dashboard (scout/club portal — single aggregated call)
132
- const dashboard = await Dashboard.getScoutDashboard();
133
- // Returns: stats (watchlisted, offersSent, playersViewed, videosWatched),
134
- // lastPlayersViewed (last 6 distinct players),
135
- // lastVideosWatched (last 6 distinct videos with player info)
136
-
137
- const videoUrl = await Dashboard.getScoutVideoPlayUrl({
138
- path: { id: 'video-uuid' },
139
- });
140
- // Returns signed play URL + records the view in video_views
141
-
142
- // Offers (conversations between scouts and players)
143
- const conversations = await Offers.listConversations({
144
- query: { filter: 'all', page: 1, pageSize: 20 },
145
- });
146
- const conversation = await Offers.createConversation({
147
- body: { playerId: 'player-uuid', initialMessage: 'Hi!' },
148
- });
149
- const messages = await Offers.listMessages({
150
- path: { id: 'conversation-uuid' },
151
- query: { page: 1, pageSize: 20 },
152
- });
153
- await Offers.sendMessage({
154
- path: { id: 'conversation-uuid' },
155
- body: { body: 'Looking forward to it!' },
156
- });
157
- await Offers.markConversationRead({ path: { id: 'conversation-uuid' } });
158
-
159
- // Organizations
160
- const org = await Organizations.getMyOrganization();
161
- await Organizations.updateMyOrganization({ body: { name: 'FC Example' } });
162
- await Organizations.registerClub({
163
- body: { name: 'FC Example', email: 'admin@example.com' },
164
- });
165
-
166
- // Clubs (coach/admin club entries within an organization)
167
- const clubs = await Clubs.getMyClubs();
168
- const club = await Clubs.createMyClub({ body: { name: 'U19 Team' } });
169
- await Clubs.updateMyClub({
170
- path: { id: 'club-uuid' },
171
- body: { name: 'U21 Team' },
172
- });
173
- await Clubs.deleteMyClub({ path: { id: 'club-uuid' } });
174
-
175
- // Challenges
176
- const challengeCatalog = await Challenges.getChallengesCatalog();
177
- const myChallenges = await Challenges.getMyChallenges();
178
-
179
- // Training
180
- const grouped = await Training.getTrainingGrouped();
181
- const trainingList = await Training.getTrainingList({
182
- query: { categoryCode: 'fitness', page: 1, pageSize: 20 },
183
- });
184
- const trainingItem = await Training.getTrainingById({
185
- path: { id: 'training-uuid' },
186
- });
187
-
188
- // Invitations (org admin invites members)
189
- await Invitations.sendInvitation({
190
- body: { email: 'coach@example.com', role: 'coach' },
191
- });
192
- const invitations = await Invitations.listInvitations();
193
- await Invitations.resendInvitation({ path: { id: 'invitation-uuid' } });
194
- await Invitations.deleteInvitation({ path: { id: 'invitation-uuid' } });
195
- await Invitations.acceptInvitation(); // called after Supabase auth callback
196
-
197
- // KYC — SmileID Enhanced Document Verification
198
- //
199
- // Two integration options:
200
-
201
- // Option A — Smile Links (recommended for web)
202
- // Backend returns a hosted SmileID URL; redirect the user to it.
203
- // SmileID handles the full capture UI and fires the webhook when done.
204
- const linkResult = await Kyc.startSmileLink({
205
- body: {
206
- country: 'gh', // stored on the verification record; does NOT filter the SmileID dropdown
207
- channel: 'web',
208
- returnUrl: 'https://yourapp.com/onboarding/kyc/callback',
209
- },
210
- });
211
- // linkResult: { url, jobId, status, retriesRemaining }
212
- // url is null when no redirect is needed (approved / blocked / processing).
213
- if (linkResult.data.url) {
214
- window.location.href = linkResult.data.url; // redirect user to SmileID
215
- }
216
- // The SmileID hosted page shows all countries configured in index.kyc_supported_id_types.
217
- // The user selects their country and document type on that page.
218
- // After the user returns to returnUrl, poll getKycStatus to confirm the result.
219
- // markSubmittedKyc is NOT needed for Smile Links — SmileID fires the webhook directly.
220
-
221
- // Option B — SDK widget (embedded, for mobile / advanced web)
222
- const kycResult = await Kyc.startKyc({
223
- body: { country: 'gh', channel: 'web' },
224
- });
225
- // kycResult: { token, partnerId, jobId, status, retriesRemaining }
226
- // Pass token + partnerId to the SmileID web SDK widget to start document capture.
227
- // After the SDK reports successful submission, call markSubmittedKyc with jobId.
228
- await Kyc.markSubmittedKyc({ body: { jobId: kycResult.data.jobId! } });
229
- // SmileID also calls POST /kyc/webhook under the configured API base/prefix
230
- // (for staging: https://staging.globalscoutme.com/api/kyc/webhook).
231
-
232
- const kycStatus = await Kyc.getKycStatus({
233
- path: { userId: supabaseAuthUuid },
234
- });
235
- // kycStatus: { status, retryCount, retriesRemaining }
236
- // status: 'pending' | 'capture_started' | 'processing' | 'approved' | 'rejected' | 'blocked'
237
- // Older API clients may still see legacy 'in_progress', which maps to 'capture_started'.
238
-
239
- // QA/staging only — skip SmileID entirely and mark the current user KYC-approved.
240
- // Returns 403 outside test-like deployments (APP_ENV and NODE_ENV must both be non-production).
241
- const qaApproved = await Kyc.qaApproveKyc();
242
- // qaApproved: { jobId, status: 'approved', retryCount, retriesRemaining, country }
243
-
244
- // Subscriptions (Stripe recurring billing)
245
- const plans = await Subscriptions.getSubscriptionPlans();
246
- // Returns: { plans: [{ id, code, name, price, currency, billingInterval, trialDays, features, displayOrder }] }
247
-
248
- const mySubscription = await Subscriptions.getMySubscription();
249
- // Returns: { status, isActive, planId, planName, billingInterval,
250
- // currentPeriodStart, currentPeriodEnd, cancelAtPeriodEnd, canceledAt, gracePeriodUntil }
251
- // status: 'inactive' when the user has never subscribed
252
-
253
- // Start a subscription — redirect the user to the returned URL
254
- const checkout = await Subscriptions.createCheckout({
255
- body: {
256
- providerProductId: 'billing-provider-products-uuid', // pick from billing_provider_products table
257
- successUrl: 'https://yourapp.com/subscription/success', // or a mobile deep-link
258
- cancelUrl: 'https://yourapp.com/subscription/cancel',
259
- },
260
- });
261
- // redirect user to checkout.url
262
-
263
- // Open the Stripe Billing Portal (manage / cancel subscription)
264
- const portal = await Subscriptions.createPortal({
265
- body: { returnUrl: 'https://yourapp.com/account' },
266
- });
267
- // redirect user to portal.url
268
-
269
- // Storage — get a signed upload URL, then PUT the file directly to Supabase Storage
270
- const { uploadUrl, token, path, bucket, expiresIn } = await Storage.createUploadUrl({
271
- body: { bucket: 'avatars', contentType: 'image/jpeg', sizeBytes: 204800 },
272
- });
273
- await fetch(uploadUrl, {
274
- method: 'PUT',
275
- body: fileBlob,
276
- headers: { 'Content-Type': 'image/jpeg' },
277
- });
278
- // persist `path` on the player record (e.g. patchMe with avatarPath: path)
279
- ```
280
-
281
- ## Updating auth token
282
-
283
- Update the Bearer token after sign-in or token refresh:
284
-
285
- ```ts
286
- client.setConfig({
287
- headers: {
288
- Authorization: `Bearer ${newAccessToken}`,
289
- },
290
- });
291
- ```
292
-
293
- ## Versioning
294
-
295
- This package is auto-generated from the backend OpenAPI spec. Version increments follow semver:
296
-
297
- - **patch** — no API shape changes
298
- - **minor** — new endpoints added
299
- - **major** — breaking changes (renamed/removed endpoints or fields)
300
-
301
- ## Package format
302
-
303
- The published package ships compiled JavaScript (`dist/`) alongside the raw TypeScript source. `main` points to `dist/index.js` so all bundlers work without extra configuration.
1
+ # @globalscoutme/api-client
2
+
3
+ Auto-generated TypeScript HTTP client for the GlobalScoutMe API, built with [`@hey-api/openapi-ts`](https://heyapi.dev/) and axios.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @globalscoutme/api-client
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ Configure the client once at app startup with your API base URL and auth token:
14
+
15
+ ```ts
16
+ import { client } from '@globalscoutme/api-client';
17
+
18
+ client.setConfig({
19
+ baseURL: 'https://your-api-url.com',
20
+ headers: {
21
+ Authorization: `Bearer ${yourSupabaseAccessToken}`,
22
+ },
23
+ });
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ All endpoints are grouped by controller and fully typed:
29
+
30
+ ```ts
31
+ import {
32
+ Auth,
33
+ Dashboard,
34
+ Players,
35
+ GlobalChat,
36
+ Profile,
37
+ Documents,
38
+ Videos,
39
+ Achievements,
40
+ Body,
41
+ Index,
42
+ Kyc,
43
+ Offers,
44
+ Organizations,
45
+ Clubs,
46
+ Challenges,
47
+ Training,
48
+ Invitations,
49
+ Storage,
50
+ Subscriptions,
51
+ } from '@globalscoutme/api-client';
52
+
53
+ // Auth
54
+ const authMe = await Auth.getAuthMe();
55
+ // Returns { id: string, userType: string | null, orgRole: 'admin' | 'user' | null }
56
+ // orgRole is the caller's organization_members role (club_admin/scouter only); null for player/coach
57
+
58
+ // Players
59
+ const player = await Players.getMe();
60
+ await Players.patchMe({ body: { fullName: 'John Doe' } });
61
+ await Players.deleteMe();
62
+ const dashboard = await Players.getMyDashboard();
63
+ await Players.heartbeat({ body: { timezone: 'Europe/Bucharest' } });
64
+ const results = await Players.searchPlayers({
65
+ body: {
66
+ search: 'John',
67
+ page: 1,
68
+ pageSize: 20,
69
+ sortBy: 'age_asc',
70
+ nationalities: ['EG', 'MA'],
71
+ positions: ['CM', 'CB'],
72
+ baseFoot: 'right',
73
+ isFreeAgent: true,
74
+ hasVideo: true,
75
+ heightOperator: 'gte',
76
+ heightCm: 180,
77
+ },
78
+ });
79
+ const playerProfile = await Players.getPlayerById({
80
+ path: { id: 'player-uuid' },
81
+ });
82
+ const playerMeasurements = await Players.getPlayerMeasurementProfile({
83
+ path: { id: 'player-uuid' },
84
+ });
85
+ // Returns { status, body, cmj, session, updatedAt } — accessible to all roles (no player-only restriction)
86
+ // status: 'missing' | 'partial' | 'ready' | 'needs_update'
87
+ const playerVideoMetrics = await Players.getPlayerVideoMetrics({
88
+ path: { id: 'player-uuid' },
89
+ });
90
+ // Returns { id, createdAt, videoDurationS, physicalMetrics, trackingQuality, trackedFrames, totalFrames }
91
+ // physicalMetrics: { totalDistance, sprintDistance, maxSpeed, averageSpeed, highIntensityRuns, sprints }
92
+ // Speed/distance values are strings with units already included (e.g. "31.0 km/h", "21.4 m")
93
+ // trackingQuality: confidence label from the analysis service (e.g. "High Confidence") — null if unavailable
94
+ // Returns 404 if the player has no completed video analysis yet
95
+ const playerReport = await Players.getPlayerReport({
96
+ path: { id: 'player-uuid' },
97
+ });
98
+ // Returns { reportReferenceId, generatedAt, scouterView: { technicalPillar, physicalPillar, tacticalPillar, mentalPillar } }
99
+ // Returns 404 if the player has no completed AI report yet
100
+
101
+ // Profile
102
+ const profile = await Profile.getProfileMe();
103
+ await Profile.updateProfileMe({ body: { dominantFoot: 'Right' } });
104
+
105
+ // Documents
106
+ const docs = await Documents.getMyDocuments();
107
+ await Documents.createDocument({ body: { type: 'license' } });
108
+
109
+ // Videos
110
+ const videos = await Videos.getMyVideos();
111
+ await Videos.createVideo({
112
+ body: { type: 'cmj', storagePath: 'path/to/video' },
113
+ });
114
+ const playUrl = await Videos.getVideoPlayUrl({ path: { id: 'video-uuid' } });
115
+ await Videos.deleteVideo({ path: { id: 'video-uuid' } });
116
+
117
+ // Achievements
118
+ const catalog = await Achievements.getAchievementsCatalog();
119
+ const myAchievements = await Achievements.getMyAchievements();
120
+
121
+ // Body measurements
122
+ const measurement = await Body.getMyMeasurements();
123
+ await Body.createMeasurement({ body: { heightCm: 180 } });
124
+
125
+ // Index (lookup tables)
126
+ const positions = await Index.getIndexItems({
127
+ path: { tableName: 'positions' },
128
+ });
129
+ const countries = await Index.getCountries();
130
+ // Returns [{ code: 'BR', name: 'Brazil', nationalityName: 'Brazilian', flagEmoji: '🇧🇷' }, ...]
131
+
132
+ // Dashboard (scout/club portal — single aggregated call)
133
+ const dashboard = await Dashboard.getScoutDashboard();
134
+ // Returns: stats (watchlisted, offersSent, playersViewed, videosWatched),
135
+ // lastPlayersViewed (last 6 distinct players),
136
+ // lastVideosWatched (last 6 distinct videos with player info)
137
+
138
+ const videoUrl = await Dashboard.getScoutVideoPlayUrl({
139
+ path: { id: 'video-uuid' },
140
+ });
141
+ // Returns signed play URL + records the view in video_views
142
+
143
+ // Offers (conversations between scouts and players)
144
+ const conversations = await Offers.listConversations({
145
+ query: { filter: 'all', page: 1, pageSize: 20 },
146
+ });
147
+ const conversation = await Offers.createConversation({
148
+ body: { playerId: 'player-uuid', initialMessage: 'Hi!' },
149
+ });
150
+ const messages = await Offers.listMessages({
151
+ path: { id: 'conversation-uuid' },
152
+ query: { page: 1, pageSize: 20 },
153
+ });
154
+ await Offers.sendMessage({
155
+ path: { id: 'conversation-uuid' },
156
+ body: { body: 'Looking forward to it!' },
157
+ });
158
+ await Offers.markConversationRead({ path: { id: 'conversation-uuid' } });
159
+
160
+ // Community Chat
161
+ const globalChat = new GlobalChat();
162
+ const globalChatStatus = await globalChat.getGlobalChatStatus();
163
+ await globalChat.joinGlobalChat({
164
+ body: {
165
+ acceptedRulesVersion: 1,
166
+ rulesLocale: 'en',
167
+ notificationsEnabled: false,
168
+ },
169
+ });
170
+ const globalChatMessages = await globalChat.listGlobalChatMessages({
171
+ query: { limit: 50 },
172
+ });
173
+ const sentGlobalChatMessage = await globalChat.sendGlobalChatMessage({
174
+ body: {
175
+ body: 'Hello, players!',
176
+ clientMessageId: '00000000-0000-4000-8000-000000000001',
177
+ },
178
+ });
179
+ await globalChat.markGlobalChatRead({
180
+ body: { messageId: sentGlobalChatMessage.data.id },
181
+ });
182
+ await globalChat.reportGlobalChatMessage({
183
+ path: { messageId: '00000000-0000-4000-8000-000000000002' },
184
+ });
185
+ await globalChat.updateGlobalChatNotifications({
186
+ body: { notificationsEnabled: true },
187
+ });
188
+ await globalChat.leaveGlobalChat();
189
+
190
+ // Organizations
191
+ const org = await Organizations.getMyOrganization();
192
+ await Organizations.updateMyOrganization({ body: { name: 'FC Example' } });
193
+ await Organizations.registerClub({
194
+ body: { name: 'FC Example', email: 'admin@example.com' },
195
+ });
196
+
197
+ // Clubs (coach/admin club entries within an organization)
198
+ const clubs = await Clubs.getMyClubs();
199
+ const club = await Clubs.createMyClub({ body: { name: 'U19 Team' } });
200
+ await Clubs.updateMyClub({
201
+ path: { id: 'club-uuid' },
202
+ body: { name: 'U21 Team' },
203
+ });
204
+ await Clubs.deleteMyClub({ path: { id: 'club-uuid' } });
205
+
206
+ // Challenges
207
+ const challengeCatalog = await Challenges.getChallengesCatalog();
208
+ const myChallenges = await Challenges.getMyChallenges();
209
+
210
+ // Training
211
+ const grouped = await Training.getTrainingGrouped();
212
+ const trainingList = await Training.getTrainingList({
213
+ query: { categoryCode: 'fitness', page: 1, pageSize: 20 },
214
+ });
215
+ const trainingItem = await Training.getTrainingById({
216
+ path: { id: 'training-uuid' },
217
+ });
218
+
219
+ // Invitations (org admin invites members)
220
+ await Invitations.sendInvitation({
221
+ body: { email: 'coach@example.com', role: 'coach' },
222
+ });
223
+ const invitations = await Invitations.listInvitations();
224
+ await Invitations.resendInvitation({ path: { id: 'invitation-uuid' } });
225
+ await Invitations.deleteInvitation({ path: { id: 'invitation-uuid' } });
226
+ await Invitations.acceptInvitation(); // called after Supabase auth callback
227
+
228
+ // KYC — SmileID Enhanced Document Verification
229
+ //
230
+ // Two integration options:
231
+
232
+ // Option A — Smile Links (recommended for web)
233
+ // Backend returns a hosted SmileID URL; redirect the user to it.
234
+ // SmileID handles the full capture UI and fires the webhook when done.
235
+ const linkResult = await Kyc.startSmileLink({
236
+ body: {
237
+ country: 'gh', // stored on the verification record; does NOT filter the SmileID dropdown
238
+ channel: 'web',
239
+ returnUrl: 'https://yourapp.com/onboarding/kyc/callback',
240
+ },
241
+ });
242
+ // linkResult: { url, jobId, status, retriesRemaining }
243
+ // url is null when no redirect is needed (approved / blocked / processing).
244
+ if (linkResult.data.url) {
245
+ window.location.href = linkResult.data.url; // redirect user to SmileID
246
+ }
247
+ // The SmileID hosted page shows all countries configured in index.kyc_supported_id_types.
248
+ // The user selects their country and document type on that page.
249
+ // After the user returns to returnUrl, poll getKycStatus to confirm the result.
250
+ // markSubmittedKyc is NOT needed for Smile Links — SmileID fires the webhook directly.
251
+
252
+ // Option B — SDK widget (embedded, for mobile / advanced web)
253
+ const kycResult = await Kyc.startKyc({
254
+ body: { country: 'gh', channel: 'web' },
255
+ });
256
+ // kycResult: { token, partnerId, jobId, status, retriesRemaining }
257
+ // Pass token + partnerId to the SmileID web SDK widget to start document capture.
258
+ // After the SDK reports successful submission, call markSubmittedKyc with jobId.
259
+ await Kyc.markSubmittedKyc({ body: { jobId: kycResult.data.jobId! } });
260
+ // SmileID also calls POST /kyc/webhook under the configured API base/prefix
261
+ // (for staging: https://staging.globalscoutme.com/api/kyc/webhook).
262
+
263
+ const kycStatus = await Kyc.getKycStatus({
264
+ path: { userId: supabaseAuthUuid },
265
+ });
266
+ // kycStatus: { status, retryCount, retriesRemaining }
267
+ // status: 'pending' | 'capture_started' | 'processing' | 'approved' | 'rejected' | 'blocked'
268
+ // Older API clients may still see legacy 'in_progress', which maps to 'capture_started'.
269
+
270
+ // QA/staging only — skip SmileID entirely and mark the current user KYC-approved.
271
+ // Returns 403 outside test-like deployments (APP_ENV and NODE_ENV must both be non-production).
272
+ const qaApproved = await Kyc.qaApproveKyc();
273
+ // qaApproved: { jobId, status: 'approved', retryCount, retriesRemaining, country }
274
+
275
+ // Subscriptions (Stripe recurring billing)
276
+ const plans = await Subscriptions.getSubscriptionPlans();
277
+ // Returns: { plans: [{ id, code, name, price, currency, billingInterval, trialDays, features, displayOrder }] }
278
+
279
+ const mySubscription = await Subscriptions.getMySubscription();
280
+ // Returns: { status, isActive, planId, planName, billingInterval,
281
+ // currentPeriodStart, currentPeriodEnd, cancelAtPeriodEnd, canceledAt, gracePeriodUntil }
282
+ // status: 'inactive' when the user has never subscribed
283
+
284
+ // Start a subscription — redirect the user to the returned URL
285
+ const checkout = await Subscriptions.createCheckout({
286
+ body: {
287
+ providerProductId: 'billing-provider-products-uuid', // pick from billing_provider_products table
288
+ successUrl: 'https://yourapp.com/subscription/success', // or a mobile deep-link
289
+ cancelUrl: 'https://yourapp.com/subscription/cancel',
290
+ },
291
+ });
292
+ // redirect user to checkout.url
293
+
294
+ // Open the Stripe Billing Portal (manage / cancel subscription)
295
+ const portal = await Subscriptions.createPortal({
296
+ body: { returnUrl: 'https://yourapp.com/account' },
297
+ });
298
+ // redirect user to portal.url
299
+
300
+ // Storage — get a signed upload URL, then PUT the file directly to Supabase Storage
301
+ const { uploadUrl, token, path, bucket, expiresIn } = await Storage.createUploadUrl({
302
+ body: { bucket: 'avatars', contentType: 'image/jpeg', sizeBytes: 204800 },
303
+ });
304
+ await fetch(uploadUrl, {
305
+ method: 'PUT',
306
+ body: fileBlob,
307
+ headers: { 'Content-Type': 'image/jpeg' },
308
+ });
309
+ // persist `path` on the player record (e.g. patchMe with avatarPath: path)
310
+ ```
311
+
312
+ ## Updating auth token
313
+
314
+ Update the Bearer token after sign-in or token refresh:
315
+
316
+ ```ts
317
+ client.setConfig({
318
+ headers: {
319
+ Authorization: `Bearer ${newAccessToken}`,
320
+ },
321
+ });
322
+ ```
323
+
324
+ ## Versioning
325
+
326
+ This package is auto-generated from the backend OpenAPI spec. Version increments follow semver:
327
+
328
+ - **patch** — no API shape changes
329
+ - **minor** — new endpoints added
330
+ - **major** — breaking changes (renamed/removed endpoints or fields)
331
+
332
+ ## Package format
333
+
334
+ The published package ships compiled JavaScript (`dist/`) alongside the raw TypeScript source. `main` points to `dist/index.js` so all bundlers work without extra configuration.