vairified 0.1.1 → 0.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/dist/index.d.cts CHANGED
@@ -1,982 +1,1227 @@
1
1
  /**
2
- * Vairified SDK Types
2
+ * HTTP transport — internal. Not part of the public API.
3
+ *
4
+ * The transport is a thin wrapper around native `fetch` that:
5
+ *
6
+ * - Injects the API key header on every request.
7
+ * - Serializes query params (dropping `undefined`) and JSON bodies.
8
+ * - Applies a request timeout via `AbortController`.
9
+ * - Maps non-2xx responses to the right typed exception.
10
+ *
11
+ * @internal
12
+ * @module
13
+ */
14
+ /**
15
+ * Values accepted as query parameters.
16
+ *
17
+ * `null` and `undefined` are silently dropped. Arrays are joined with
18
+ * `','`. Everything else is stringified.
19
+ *
20
+ * @internal
21
+ */
22
+ type QueryParams = Readonly<Record<string, string | number | boolean | readonly (string | number)[] | null | undefined>>;
23
+ /**
24
+ * Options passed to the internal HTTP layer.
25
+ *
26
+ * @internal
27
+ */
28
+ interface RequestOptions {
29
+ readonly method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
30
+ readonly path: string;
31
+ readonly query?: QueryParams;
32
+ readonly body?: unknown;
33
+ }
34
+ /**
35
+ * Transport configuration.
36
+ *
37
+ * @internal
38
+ */
39
+ interface TransportConfig {
40
+ readonly baseUrl: string;
41
+ readonly apiKey: string;
42
+ readonly timeoutMs: number;
43
+ readonly fetch: typeof fetch;
44
+ }
45
+ /**
46
+ * Internal HTTP client. Holds no state between requests — each call
47
+ * builds a fresh `Request`, runs it, and returns the parsed body.
48
+ *
49
+ * @internal
50
+ */
51
+ declare class HttpTransport {
52
+ #private;
53
+ constructor(config: TransportConfig);
54
+ request<T>(options: RequestOptions): Promise<T>;
55
+ }
56
+
57
+ /**
58
+ * Vairified SDK — Partner API v1 wire types.
59
+ *
60
+ * These are the raw shapes the server returns or accepts. Request
61
+ * types (e.g. {@link MatchBatch}, {@link SearchFilters}) are used as-is
62
+ * by the SDK. Response types (e.g. {@link PartnerMemberWire}) are
63
+ * wrapped in class instances by the SDK — see the `models/` folder.
3
64
  *
4
65
  * @module
5
66
  */
6
67
  /**
7
- * Options for initializing the Vairified client.
68
+ * Environment preset for the Vairified API.
8
69
  *
9
- * @category Types
70
+ * @category Client
71
+ */
72
+ type VairifiedEnvironment = 'production' | 'staging' | 'local';
73
+ /**
74
+ * Options passed to the {@link Vairified} constructor.
75
+ *
76
+ * @category Client
10
77
  */
11
78
  interface VairifiedOptions {
12
- /** API key for authentication */
79
+ /** Partner API key (`vair_pk_...`). Falls back to `VAIRIFIED_API_KEY`. */
13
80
  apiKey?: string;
14
- /** Environment preset: "production" (default), "staging", "local" */
15
- env?: 'production' | 'staging' | 'local';
16
- /** Override API base URL. Takes precedence over env. */
81
+ /** Environment preset. Falls back to `VAIRIFIED_ENV` or `'production'`. */
82
+ env?: VairifiedEnvironment;
83
+ /** Explicit base URL. Takes precedence over `env`. */
17
84
  baseUrl?: string;
18
- /** Request timeout in milliseconds (default: 30000) */
19
- timeout?: number;
85
+ /** Request timeout in milliseconds. Defaults to 30_000 (30s). */
86
+ timeoutMs?: number;
87
+ /**
88
+ * Inject a custom `fetch` implementation. Defaults to the global
89
+ * `fetch`. Useful for test shims or non-Node environments.
90
+ */
91
+ fetch?: typeof fetch;
20
92
  }
21
93
  /**
22
- * A single rating split with metadata.
94
+ * Normalized gender tokens emitted by the Partner API.
95
+ *
96
+ * @category Members
97
+ */
98
+ type Gender = 'MALE' | 'FEMALE' | 'OTHER' | 'UNKNOWN';
99
+ /**
100
+ * Raw rating split — one slice of a player's rating for a specific
101
+ * category × age bracket.
23
102
  *
24
- * @category Types
103
+ * @category Members
25
104
  */
26
- interface RatingSplitData {
27
- /** The rating value (may be string from API) */
28
- rating: string | number;
29
- /** Abbreviation (e.g., "VG", "50+") */
30
- abbr: string;
31
- /** Date of last match in this category */
32
- date_played?: string;
105
+ interface RatingSplitWire {
106
+ readonly rating: number;
107
+ readonly abbr: string;
33
108
  }
34
109
  /**
35
- * Rating splits from API - map of category to rating data.
110
+ * Raw sport rating — a player's ratings for one sport.
36
111
  *
37
- * @category Types
112
+ * @category Members
38
113
  */
39
- type RatingSplitsData = Record<string, RatingSplitData | number>;
114
+ interface SportRatingWire {
115
+ readonly rating: number;
116
+ readonly abbr: string;
117
+ readonly ratingSplits: Readonly<Record<string, RatingSplitWire>>;
118
+ }
40
119
  /**
41
- * Player data from getMember endpoint (requires OAuth connection).
120
+ * Grouped member status flags.
42
121
  *
43
- * @category Types
122
+ * @category Members
44
123
  */
45
- interface MemberData {
46
- /** External player ID (vair_mem_xxx format) */
47
- id: string;
48
- firstName?: string;
49
- lastName?: string;
50
- /** Email (only if profile:email scope granted) */
51
- email?: string;
52
- rating?: number;
53
- isVairified?: boolean;
54
- ratingSplits?: RatingSplitsData;
55
- city?: string;
56
- state?: string;
57
- country?: string;
58
- /** Scopes the player granted to your app */
59
- grantedScopes?: string[];
124
+ interface MemberStatusWire {
125
+ readonly isVairified: boolean;
126
+ readonly isWheelchair: boolean;
127
+ readonly isAmbassador: boolean;
128
+ readonly isRater: boolean;
129
+ readonly isConnected: boolean;
60
130
  }
61
131
  /**
62
- * Player data from search endpoint (public, limited data).
132
+ * Raw partner-facing member record returned by the Partner API.
63
133
  *
64
- * @category Types
134
+ * @category Members
65
135
  */
66
- interface PlayerSearchData {
67
- /** External player ID (vair_mem_xxx format) */
68
- id: string;
69
- /** Display name (First Name + Last Initial for privacy) */
70
- displayName: string;
71
- city?: string;
72
- state?: string;
73
- country?: string;
74
- rating?: number;
75
- isVairified?: boolean;
76
- /** Whether player has connected to your app */
77
- isConnected?: boolean;
136
+ interface PartnerMemberWire {
137
+ readonly memberId: number;
138
+ readonly id?: string;
139
+ readonly firstName: string;
140
+ readonly lastName: string;
141
+ readonly fullName: string;
142
+ readonly displayName: string;
143
+ readonly age?: number;
144
+ readonly city?: string;
145
+ readonly state?: string;
146
+ readonly zip?: string;
147
+ readonly country?: string;
148
+ readonly gender?: Gender;
149
+ readonly status: MemberStatusWire;
150
+ readonly sport?: Readonly<Record<string, SportRatingWire>>;
151
+ readonly activeLeagues?: readonly string[];
152
+ readonly email?: string;
153
+ readonly grantedScopes?: readonly string[];
78
154
  }
79
155
  /**
80
- * Filters for player search.
156
+ * Raw rating change notification.
157
+ *
158
+ * @category Members
159
+ */
160
+ interface PartnerRatingUpdateWire {
161
+ readonly memberId: number;
162
+ readonly id?: string;
163
+ readonly displayName?: string;
164
+ readonly sport?: string;
165
+ readonly previousRating?: number;
166
+ readonly newRating?: number;
167
+ readonly changedAt?: string;
168
+ readonly ratingSplits?: Readonly<Record<string, RatingSplitWire>>;
169
+ }
170
+ /**
171
+ * Filters accepted by {@link MembersResource.search}.
172
+ *
173
+ * Most callers pass these as keyword arguments directly to `search()`;
174
+ * the SDK serializes them to query parameters for you.
81
175
  *
82
- * @category Types
176
+ * @category Members
83
177
  */
84
178
  interface SearchFilters {
85
- /** Name search (partial match) */
179
+ /** Sport code (e.g. `'pickleball'`) or list of codes. */
180
+ sport?: string | readonly string[];
181
+ /** Partial name match (first or last). */
86
182
  name?: string;
87
- /** City filter */
183
+ /** Exact numeric member ID. */
184
+ memberId?: number | string;
88
185
  city?: string;
89
- /** State code (e.g., "TX") */
90
186
  state?: string;
91
- /** Country code (e.g., "US") */
92
187
  country?: string;
93
- /** ZIP/postal code */
94
- zipCode?: string;
95
- /** Minimum rating (2.0-8.0) */
188
+ zip?: string;
189
+ location?: string;
190
+ gender?: Gender | Lowercase<Gender>;
191
+ /** When `true`, only return verified players. */
192
+ vairifiedOnly?: boolean;
193
+ /** When `true`, only return wheelchair players. */
194
+ wheelchair?: boolean;
195
+ /** Lower rating bound (2.0–8.0). */
96
196
  ratingMin?: number;
97
- /** Maximum rating (2.0-8.0) */
197
+ /** Upper rating bound (2.0–8.0). */
98
198
  ratingMax?: number;
99
- /** Gender filter */
100
- gender?: 'MALE' | 'FEMALE';
101
- /** Only verified players */
102
- vairifiedOnly?: boolean;
103
- /** Exact age */
199
+ /** Exact age. */
104
200
  age?: number;
105
- /** Minimum age */
201
+ /** Lower age bound. */
106
202
  ageMin?: number;
107
- /** Maximum age */
203
+ /** Upper age bound. */
108
204
  ageMax?: number;
109
- /** Field to sort by */
110
205
  sortBy?: string;
111
- /** Sort direction */
112
206
  sortOrder?: 'asc' | 'desc';
113
- /** Page number (1-indexed) */
114
- page?: number;
115
- /** Results per page (max 100) */
116
- limit?: number;
207
+ /** Page size requested per HTTP request (server cap 100). Default 20. */
208
+ pageSize?: number;
209
+ /**
210
+ * Maximum total results to iterate. When set, the async iterator
211
+ * stops after yielding this many items, regardless of page count.
212
+ */
213
+ maxResults?: number;
117
214
  }
118
215
  /**
119
- * Match input for creating a Match.
216
+ * One scored game within a {@link MatchInput}.
217
+ *
218
+ * `scores` contains one integer per team, in the same order as the
219
+ * parent match's `teams` list. For a standard 2-team game that's
220
+ * `[team1Score, team2Score]`. Longer lists are supported for
221
+ * n-team matches.
120
222
  *
121
- * @category Types
223
+ * All other fields override the parent match's defaults for this
224
+ * specific game (e.g. a championship game played to 15 when the rest
225
+ * of the match was to 11).
226
+ *
227
+ * @category Matches
122
228
  */
123
- interface MatchInput {
124
- /** Event/tournament name */
125
- event: string;
126
- /** Bracket/division name */
127
- bracket: string;
128
- /** Match date and time */
129
- date: Date | string;
130
- /** Team 1 player IDs (1 for singles, 2 for doubles) */
131
- team1: [string] | [string, string];
132
- /** Team 2 player IDs (1 for singles, 2 for doubles) */
133
- team2: [string] | [string, string];
134
- /** Game scores as [team1Score, team2Score] tuples */
135
- scores: [number, number][];
136
- /** Match type (default: "SIDEOUT") */
137
- matchType?: string;
138
- /** Match source (default: "PARTNER") */
139
- source?: string;
140
- /** Location (optional) */
141
- location?: string;
142
- /** Unique identifier (auto-generated if not provided) */
143
- identifier?: string;
229
+ interface GameInput {
230
+ readonly scores: readonly number[];
231
+ readonly identifier?: string;
232
+ readonly winScore?: number;
233
+ readonly winBy?: number;
144
234
  }
145
235
  /**
146
- * Match data as sent to API.
236
+ * One match to submit in a {@link MatchBatch}.
237
+ *
238
+ * A match is n-team × n-game:
239
+ *
240
+ * - `teams: [['p1', 'p2'], ['p3', 'p4']]` — standard doubles
241
+ * - `teams: [['p1'], ['p2']]` — singles
242
+ * - `teams: [['p1'], ['p2'], ['p3']]` — 3-way round robin
243
+ *
244
+ * Scores in each {@link GameInput} are parallel to the `teams` order.
147
245
  *
148
- * @category Types
246
+ * @category Matches
149
247
  */
150
- interface MatchApiData {
151
- identifier: string;
152
- bracket: string;
153
- event: string;
154
- format: 'SINGLES' | 'DOUBLES';
155
- matchDate: string;
156
- matchSource: string;
157
- matchType: string;
158
- location?: string;
159
- teamA: {
160
- player1: string;
161
- player2?: string;
162
- game1?: number;
163
- game2?: number;
164
- game3?: number;
165
- game4?: number;
166
- game5?: number;
167
- };
168
- teamB: {
169
- player1: string;
170
- player2?: string;
171
- game1?: number;
172
- game2?: number;
173
- game3?: number;
174
- game4?: number;
175
- game5?: number;
176
- };
177
- }
178
- /**
179
- * Response from match submission.
180
- *
181
- * @category Types
182
- */
183
- interface MatchResultData {
184
- /** Whether submission succeeded */
185
- success: boolean;
186
- /** Number of matches processed */
187
- numMatches: number;
188
- /** Total number of games recorded */
189
- numGames: number;
190
- /** Whether this was a dry-run (validation only) */
191
- dryRun?: boolean;
192
- /** Human-readable result message */
193
- message?: string;
194
- /** List of validation/processing errors */
195
- errors?: string[];
196
- }
197
- /**
198
- * Rating update from API.
199
- *
200
- * @category Types
201
- */
202
- interface RatingUpdateData {
203
- /** External player ID (vair_mem_xxx format) */
204
- id: string;
205
- memberName?: string;
206
- previousRating?: number;
207
- newRating?: number;
208
- changedAt?: string;
209
- ratingSplits?: RatingSplitsData;
210
- }
211
- /**
212
- * Search results from API.
213
- *
214
- * @category Types
215
- */
216
- interface SearchResultsData {
217
- players: PlayerSearchData[];
218
- total: number;
219
- page: number;
220
- limit: number;
248
+ interface MatchInput {
249
+ readonly identifier: string;
250
+ readonly teams: readonly (readonly string[])[];
251
+ readonly games: readonly GameInput[];
252
+ readonly sport?: string;
253
+ readonly bracket?: string;
254
+ readonly event?: string;
255
+ readonly location?: string;
256
+ readonly matchDate?: string;
257
+ readonly matchSource?: string;
258
+ readonly matchType?: string;
259
+ readonly winScore?: number;
260
+ readonly winBy?: number;
261
+ readonly extras?: Readonly<Record<string, unknown>>;
262
+ readonly originalId?: string;
263
+ readonly originalType?: string;
264
+ readonly clubId?: number;
221
265
  }
222
-
223
266
  /**
224
- * Vairified SDK Models
267
+ * Compressed bulk match submission.
225
268
  *
226
- * Rich model classes with methods for easy API interaction.
269
+ * Top-level fields are defaults applied to every match in the
270
+ * {@link matches} list. Any match can override any field. `sport`,
271
+ * `winScore`, and `winBy` are **required** at the batch level — partners
272
+ * must tell the rater which sport the matches are in and what the
273
+ * winning conditions were so scores can be interpreted correctly.
227
274
  *
228
- * @module
275
+ * @category Matches
229
276
  */
230
-
231
- /** Union type for player data from different endpoints */
232
- type PlayerData = MemberData | PlayerSearchData;
277
+ interface MatchBatch {
278
+ readonly sport: string;
279
+ readonly winScore: number;
280
+ readonly winBy: number;
281
+ readonly matches: readonly MatchInput[];
282
+ readonly bracket?: string;
283
+ readonly event?: string;
284
+ readonly location?: string;
285
+ readonly matchDate?: string;
286
+ readonly matchSource?: string;
287
+ readonly matchType?: string;
288
+ readonly extras?: Readonly<Record<string, unknown>>;
289
+ readonly identifier?: string;
290
+ readonly originalId?: string;
291
+ readonly originalType?: string;
292
+ readonly clubId?: number;
293
+ readonly dryRun?: boolean;
294
+ }
233
295
  /**
234
- * A single rating split with metadata.
296
+ * Raw result from a batch submission.
235
297
  *
236
- * @category Models
298
+ * @category Matches
237
299
  */
238
- declare class RatingSplit {
239
- /** The rating value */
240
- readonly rating: number;
241
- /** Abbreviation (e.g., "VG", "50+") */
242
- readonly abbr: string;
243
- /** Date of last match in this category */
244
- readonly datePlayed?: string;
245
- constructor(data: RatingSplitData | number);
246
- }
247
- /**
248
- * Rating breakdown by category.
249
- *
250
- * Access ratings by category name or use convenience properties.
251
- *
252
- * @category Models
253
- */
254
- declare class RatingSplits {
255
- /** Map of category names to rating splits */
256
- readonly splits: Map<string, RatingSplit>;
257
- constructor(data?: RatingSplitsData);
258
- /** Get rating for a category */
259
- get(category: string): number | undefined;
260
- /** Open division rating */
261
- get open(): number | undefined;
262
- /** Gender-specific rating (same gender doubles) */
263
- get gender(): number | undefined;
264
- /** Mixed doubles rating */
265
- get mixed(): number | undefined;
266
- /** Recreational rating */
267
- get recreational(): number | undefined;
268
- /** Singles rating */
269
- get singles(): number | undefined;
270
- /** Best available verified rating */
271
- get best(): number | undefined;
272
- /** Convert to plain object */
273
- toJSON(): Record<string, {
274
- rating: number;
275
- abbr: string;
276
- }>;
277
- }
278
- /**
279
- * A player in the Vairified system.
280
- *
281
- * From public search, only limited data is available (display name, location, rating).
282
- * For full profile data, use getMember() with OAuth consent.
283
- *
284
- * @category Models
285
- */
286
- declare class Player {
287
- /** External player ID (vair_mem_xxx format) */
288
- readonly id: string;
289
- /** Display name (First Name + Last Initial from search) */
290
- readonly displayName?: string;
291
- /** First name (only from connected member) */
292
- readonly firstName?: string;
293
- /** Last name (only from connected member) */
294
- readonly lastName?: string;
295
- /** Primary/overall rating (2.0-8.0) */
296
- readonly rating: number;
297
- /** Whether player is verified */
298
- readonly isVairified: boolean;
299
- /** Whether player has connected to your app */
300
- readonly isConnected: boolean;
301
- /** Ratings by category (only from connected member) */
302
- readonly ratingSplits: RatingSplits;
303
- /** City */
304
- readonly city?: string;
305
- /** State code */
306
- readonly state?: string;
307
- /** Country code */
308
- readonly country?: string;
309
- protected _client?: Vairified;
310
- constructor(data: PlayerData, client?: Vairified);
311
- /** Full name (or display name if full name not available) */
312
- get name(): string;
313
- /** Best verified rating */
314
- get verifiedRating(): number | undefined;
315
- toString(): string;
300
+ interface MatchBatchResultWire {
301
+ readonly success: boolean;
302
+ readonly numMatches: number;
303
+ readonly numGames: number;
304
+ readonly dryRun?: boolean;
305
+ readonly message?: string;
306
+ readonly errors?: readonly string[];
316
307
  }
317
308
  /**
318
- * A member with full profile access (requires OAuth connection).
309
+ * Filters for {@link LeaderboardResource.list}.
319
310
  *
320
- * Only accessible for players who have connected their account via OAuth.
321
- *
322
- * @category Models
311
+ * @category Leaderboards
323
312
  */
324
- declare class Member extends Player {
325
- /** Email address (only if profile:email scope granted) */
326
- readonly email?: string;
327
- /** Scopes the player granted to your app */
328
- readonly grantedScopes: string[];
329
- constructor(data: MemberData, client?: Vairified);
330
- /** Check if the player has granted a specific scope */
331
- hasScope(scope: string): boolean;
332
- /** Refresh member data from API */
333
- refresh(): Promise<Member>;
313
+ interface LeaderboardOptions {
314
+ readonly category?: string;
315
+ readonly ageBracket?: string;
316
+ readonly scope?: string;
317
+ readonly state?: string;
318
+ readonly city?: string;
319
+ readonly clubId?: string;
320
+ readonly gender?: Gender | Lowercase<Gender>;
321
+ readonly verifiedOnly?: boolean;
322
+ readonly minGames?: number;
323
+ readonly limit?: number;
324
+ readonly offset?: number;
325
+ readonly search?: string;
334
326
  }
335
327
  /**
336
- * A match to submit to the Vairified Partner API.
328
+ * Options for {@link LeaderboardResource.rank}.
337
329
  *
338
- * @category Models
339
- *
340
- * @example
341
- * ```ts
342
- * // Doubles match: 11-9, 11-7
343
- * const match = new Match({
344
- * event: 'Weekly League',
345
- * bracket: '4.0 Doubles',
346
- * date: new Date(),
347
- * team1: ['player1_id', 'player2_id'],
348
- * team2: ['player3_id', 'player4_id'],
349
- * scores: [[11, 9], [11, 7]],
350
- * });
351
- *
352
- * // Singles match: 11-8, 9-11, 11-6
353
- * const match = new Match({
354
- * event: 'Club Singles',
355
- * bracket: 'Open Singles',
356
- * date: new Date(),
357
- * team1: ['player1_id'],
358
- * team2: ['player2_id'],
359
- * scores: [[11, 8], [9, 11], [11, 6]],
360
- * });
361
- * ```
330
+ * @category Leaderboards
362
331
  */
363
- declare class Match {
364
- /** Event/tournament name */
365
- readonly event: string;
366
- /** Bracket/division name */
367
- readonly bracket: string;
368
- /** Match date */
369
- readonly date: Date;
370
- /** Team 1 player IDs */
371
- readonly team1: readonly string[];
372
- /** Team 2 player IDs */
373
- readonly team2: readonly string[];
374
- /** Game scores */
375
- readonly scores: readonly [number, number][];
376
- /** Match type */
377
- readonly matchType: string;
378
- /** Match source */
379
- readonly source: string;
380
- /** Location */
381
- readonly location?: string;
382
- /** Unique identifier */
383
- readonly identifier: string;
384
- /** Match ID (set after submission) */
385
- id?: string;
386
- constructor(data: MatchInput);
387
- /** Match format: SINGLES or DOUBLES */
388
- get format(): 'SINGLES' | 'DOUBLES';
389
- /** Team that won (1 or 2). Returns 0 if tie. */
390
- get winner(): 0 | 1 | 2;
391
- /** Score summary like "11-9, 11-7" */
392
- get scoreSummary(): string;
393
- /** Convert to API request format */
394
- toJSON(): MatchApiData;
332
+ interface PlayerRankOptions {
333
+ readonly category?: string;
334
+ readonly ageBracket?: string;
335
+ readonly scope?: string;
336
+ readonly state?: string;
337
+ readonly city?: string;
338
+ readonly clubId?: string;
339
+ /** Number of players on either side of the target. Default 5. */
340
+ readonly contextSize?: number;
395
341
  }
396
342
  /**
397
- * Result of a match submission.
343
+ * Wire shape for tournament import response.
398
344
  *
399
- * @category Models
345
+ * @category Matches
400
346
  */
401
- declare class MatchResult {
402
- /** Whether submission succeeded */
347
+ interface TournamentImportResultWire {
403
348
  readonly success: boolean;
404
- /** Number of matches processed */
405
- readonly numMatches: number;
406
- /** Number of games recorded */
407
- readonly numGames: number;
408
- /** Whether this was a dry-run (validation only) */
409
- readonly dryRun: boolean;
410
- /** Human-readable result message */
349
+ readonly matchesImported: number;
350
+ readonly gamesRecorded: number;
351
+ readonly ghostPlayersCreated: number;
352
+ readonly existingPlayersMatched: number;
353
+ readonly dryRun?: boolean;
411
354
  readonly message?: string;
412
- /** List of validation/processing errors */
413
- readonly errors: string[];
414
- constructor(data: MatchResultData);
415
- /** Alias for dryRun */
416
- get isDryRun(): boolean;
417
- /** Returns true if submission succeeded without errors */
418
- get ok(): boolean;
355
+ readonly errors?: readonly string[];
419
356
  }
420
357
  /**
421
- * A rating change notification.
358
+ * Wire shape for a single webhook delivery attempt.
422
359
  *
423
- * @category Models
360
+ * @category Webhooks
424
361
  */
425
- declare class RatingUpdate {
426
- /** External player ID (vair_mem_xxx format) */
362
+ interface WebhookDeliveryWire {
427
363
  readonly id: string;
428
- /** Member name */
429
- readonly memberName?: string;
430
- /** Previous rating */
431
- readonly previousRating: number;
432
- /** New rating */
433
- readonly newRating: number;
434
- /** When the change occurred */
435
- readonly changedAt: Date;
436
- /** Updated rating splits */
437
- readonly ratingSplits: RatingSplits;
438
- private _client?;
439
- constructor(data: RatingUpdateData, client?: Vairified);
440
- /** Amount of rating change */
441
- get change(): number;
442
- /** Whether rating improved */
443
- get improved(): boolean;
444
- /** Fetch the member associated with this update */
445
- getMember(): Promise<Member>;
446
- toString(): string;
364
+ readonly event: string;
365
+ readonly url: string;
366
+ readonly statusCode: number | null;
367
+ readonly responseBody: string | null;
368
+ readonly errorMessage: string | null;
369
+ readonly attempts: number;
370
+ readonly maxAttempts: number;
371
+ readonly lastAttemptAt: string;
372
+ readonly nextRetryAt: string | null;
373
+ readonly completedAt: string | null;
374
+ readonly createdAt: string;
375
+ readonly payload: Record<string, unknown>;
447
376
  }
448
377
  /**
449
- * Paginated search results.
378
+ * Wire shape for paginated webhook delivery results.
450
379
  *
451
- * @category Models
380
+ * @category Webhooks
452
381
  */
453
- declare class SearchResults implements Iterable<Player> {
454
- /** List of players */
455
- readonly players: Player[];
456
- /** Total matching players */
382
+ interface WebhookDeliveriesResultWire {
383
+ readonly deliveries: readonly WebhookDeliveryWire[];
457
384
  readonly total: number;
458
- /** Current page */
459
- readonly page: number;
460
- /** Results per page */
461
- readonly limit: number;
462
- private _client?;
463
- private _filters;
464
- constructor(data: SearchResultsData, client?: Vairified, filters?: SearchFilters);
465
- /** Whether more results are available */
466
- get hasMore(): boolean;
467
- /** Total number of pages */
468
- get pages(): number;
469
- /** Number of players in current page */
470
- get length(): number;
471
- /** Get player by index */
472
- at(index: number): Player | undefined;
473
- /** Iterate over players */
474
- [Symbol.iterator](): Iterator<Player>;
475
- /** Fetch next page of results */
476
- nextPage(): Promise<SearchResults>;
477
385
  }
478
-
479
386
  /**
480
- * Vairified OAuth Helpers
387
+ * Error envelope commonly returned by the Partner API on non-2xx status.
481
388
  *
482
- * Utilities for implementing the "Connect with Vairified" OAuth flow.
389
+ * @category Errors
390
+ */
391
+ interface ApiErrorResponse {
392
+ readonly message?: string;
393
+ readonly error?: string;
394
+ readonly statusCode?: number;
395
+ }
396
+
397
+ /**
398
+ * {@link LeaderboardResource} — read-only leaderboard queries.
483
399
  *
484
400
  * @module
485
401
  */
402
+
486
403
  /**
487
- * Available OAuth scopes with descriptions.
404
+ * Read-only leaderboard queries.
488
405
  *
489
- * @category OAuth
406
+ * @category Resources
490
407
  */
491
- declare const SCOPES: {
492
- readonly 'profile:read': "Access your name, location, and verification status";
493
- readonly 'profile:email': "Access your email address";
494
- readonly 'rating:read': "View your current rating and rating splits";
495
- readonly 'rating:history': "View your complete rating history";
496
- readonly 'match:submit': "Submit match results on your behalf";
497
- readonly 'webhook:subscribe': "Receive notifications when your rating changes";
498
- };
408
+ declare class LeaderboardResource {
409
+ #private;
410
+ /** @internal */
411
+ constructor(http: HttpTransport);
412
+ /** Fetch a leaderboard page with optional filters. */
413
+ list(options?: LeaderboardOptions): Promise<Record<string, unknown>>;
414
+ /** Fetch a specific player's rank plus nearby players. */
415
+ rank(playerId: string, options?: PlayerRankOptions): Promise<Record<string, unknown>>;
416
+ /** List available leaderboard categories, brackets, and scopes. */
417
+ categories(): Promise<Record<string, unknown>>;
418
+ }
419
+
499
420
  /**
500
- * Available OAuth scope keys.
421
+ * {@link MatchBatchResult} — result of a batch match submission.
501
422
  *
502
- * @category OAuth
423
+ * @module
503
424
  */
504
- type OAuthScope = keyof typeof SCOPES;
425
+
505
426
  /**
506
- * Default scopes requested for new connections.
427
+ * Result of a {@link MatchesResource.submit} call.
507
428
  *
508
- * @category OAuth
429
+ * {@link success} is `true` only when every match in the batch was
430
+ * accepted. Check {@link errors} for per-match validation failures.
431
+ *
432
+ * @category Matches
509
433
  */
510
- declare const DEFAULT_SCOPES: OAuthScope[];
434
+ declare class MatchBatchResult {
435
+ readonly success: boolean;
436
+ readonly numMatches: number;
437
+ readonly numGames: number;
438
+ readonly dryRun: boolean | null;
439
+ readonly message: string | null;
440
+ readonly errors: readonly string[] | null;
441
+ constructor(wire: MatchBatchResultWire);
442
+ /** Shorthand: successful submission with zero errors. */
443
+ get ok(): boolean;
444
+ /** Whether this was a dry-run (validation only, nothing persisted). */
445
+ get isDryRun(): boolean;
446
+ toString(): string;
447
+ }
448
+
511
449
  /**
512
- * OAuth configuration for a partner application.
450
+ * {@link TournamentImportResult} — result of a tournament import submission.
513
451
  *
514
- * @category OAuth
452
+ * @module
515
453
  */
516
- interface OAuthConfig {
517
- /** Partner API key */
518
- apiKey: string;
519
- /** Your application's callback URL */
520
- redirectUri: string;
521
- /** Vairified API base URL */
522
- baseUrl?: string;
523
- }
454
+
524
455
  /**
525
- * Response from starting an OAuth authorization.
456
+ * Result of a tournament import submission.
526
457
  *
527
- * @category OAuth
458
+ * @category Matches
528
459
  */
529
- interface AuthorizationResponse {
530
- /** Full URL to redirect the user to */
531
- authorizationUrl: string;
532
- /** Authorization code (for internal tracking) */
533
- code: string;
534
- /** CSRF state parameter */
535
- state?: string;
460
+ declare class TournamentImportResult {
461
+ readonly success: boolean;
462
+ readonly matchesImported: number;
463
+ readonly gamesRecorded: number;
464
+ readonly ghostPlayersCreated: number;
465
+ readonly existingPlayersMatched: number;
466
+ readonly dryRun: boolean;
467
+ readonly message: string | undefined;
468
+ readonly errors: readonly string[];
469
+ /** @internal */
470
+ constructor(wire: TournamentImportResultWire);
471
+ /** True when the import succeeded without errors. */
472
+ get ok(): boolean;
536
473
  }
474
+
537
475
  /**
538
- * Response from exchanging an authorization code for tokens.
476
+ * {@link MatchesResource} — bulk match submission.
539
477
  *
540
- * @category OAuth
478
+ * @module
541
479
  */
542
- interface TokenResponse {
543
- /** Access token for API requests */
544
- accessToken: string;
545
- /** Refresh token for obtaining new access tokens */
546
- refreshToken?: string;
547
- /** Token expiration in seconds */
548
- expiresIn: number;
549
- /** Granted scopes */
550
- scope: string[];
551
- /** Connected player's external ID */
552
- playerId: string;
480
+
481
+ /**
482
+ * Match submission — one call submits a full batch.
483
+ *
484
+ * @category Resources
485
+ */
486
+ declare class MatchesResource {
487
+ #private;
488
+ /** @internal */
489
+ constructor(http: HttpTransport);
490
+ /**
491
+ * Submit a {@link MatchBatch} for rating calculation.
492
+ *
493
+ * All players in every match must have granted the `user:match:submit`
494
+ * scope via OAuth (unless your API key has the
495
+ * `user:match:submit:trusted` scope, which skips per-player consent).
496
+ *
497
+ * Set `batch.dryRun = true` to validate without persisting.
498
+ *
499
+ * ```ts
500
+ * const result = await client.matches.submit({
501
+ * sport: 'pickleball',
502
+ * winScore: 11,
503
+ * winBy: 2,
504
+ * bracket: '4.0 Doubles',
505
+ * event: 'Weekly League',
506
+ * matchDate: '2026-04-11T14:00:00Z',
507
+ * matches: [
508
+ * {
509
+ * identifier: 'm1',
510
+ * teams: [['vair_mem_aaa', 'vair_mem_bbb'],
511
+ * ['vair_mem_ccc', 'vair_mem_ddd']],
512
+ * games: [{ scores: [11, 8] }, { scores: [11, 5] }],
513
+ * },
514
+ * ],
515
+ * });
516
+ * if (result.ok) {
517
+ * console.log(`Submitted ${result.numGames} games`);
518
+ * }
519
+ * ```
520
+ */
521
+ submit(batch: MatchBatch): Promise<MatchBatchResult>;
522
+ /**
523
+ * Import tournament results.
524
+ *
525
+ * The request body is a free-form JSON object whose structure is
526
+ * defined by the Vairified tournament import schema. Set
527
+ * `body.dryRun = true` to validate without persisting.
528
+ *
529
+ * @param body - Tournament import payload.
530
+ * @returns {@link TournamentImportResult} with match/game counts.
531
+ * @category Matches
532
+ *
533
+ * @example
534
+ * ```ts
535
+ * const result = await client.matches.tournamentImport({
536
+ * sport: 'pickleball',
537
+ * tournamentName: 'Spring Classic',
538
+ * matches: [...],
539
+ * });
540
+ * if (result.ok) {
541
+ * console.log(`Imported ${result.matchesImported} matches`);
542
+ * }
543
+ * ```
544
+ */
545
+ tournamentImport(body: Record<string, unknown>): Promise<TournamentImportResult>;
546
+ /** Send a test payload to a webhook URL. */
547
+ testWebhook(webhookUrl: string): Promise<Record<string, unknown>>;
553
548
  }
549
+
554
550
  /**
555
- * Build the URL to redirect users to for OAuth authorization.
551
+ * {@link SportRating} — a player's ratings for one sport.
556
552
  *
557
- * This is a helper for building the URL manually. In most cases,
558
- * you should use the Vairified client's OAuth methods instead.
553
+ * @module
554
+ */
555
+
556
+ /**
557
+ * A player's ratings for a single sport.
559
558
  *
560
- * @param config - OAuth configuration
561
- * @param scopes - Permission scopes to request
562
- * @param state - CSRF protection state parameter
563
- * @returns URL to redirect the user to
559
+ * The top-level `rating` / `abbr` is the primary rating for that sport
560
+ * (conventionally the overall-open bracket). Every category × age
561
+ * bracket the player has played is also available via {@link get},
562
+ * {@link has}, subscript-style access, and iteration.
564
563
  *
565
- * @example
566
564
  * ```ts
567
- * const url = getAuthorizationUrl(
568
- * {
569
- * apiKey: 'vair_pk_xxx',
570
- * redirectUri: 'https://myapp.com/oauth/callback',
571
- * },
572
- * ['profile:read', 'rating:read'],
573
- * );
574
- * // Redirect user to this URL
575
- * window.location.href = url;
565
+ * const pb = member.sport.get('pickleball');
566
+ * if (pb) {
567
+ * console.log(pb.rating, pb.abbr); // 3.915 VO
568
+ * console.log(pb.get('overall-open')?.rating); // 3.915
569
+ * console.log(pb.has('singles-40+')); // false
570
+ * console.log(pb.size); // 3
571
+ * for (const [key, split] of pb) {
572
+ * console.log(key, split.rating);
573
+ * }
574
+ * }
576
575
  * ```
577
576
  *
578
- * @category OAuth
577
+ * @category Members
579
578
  */
580
- declare function getAuthorizationUrl(config: OAuthConfig, scopes?: OAuthScope[], state?: string): string;
579
+ declare class SportRating {
580
+ #private;
581
+ /** Primary rating for this sport. */
582
+ readonly rating: number;
583
+ /** Category abbreviation for the primary rating (e.g. `'VO'`). */
584
+ readonly abbr: string;
585
+ constructor(wire: SportRatingWire);
586
+ /**
587
+ * Look up a rating split by key (e.g. `'overall-open'`,
588
+ * `'singles-12-13'`, `'gender-40+'`). Returns `undefined` if the
589
+ * player has no rating for that bracket.
590
+ */
591
+ get(key: string): RatingSplitWire | undefined;
592
+ /** Whether the player has a rating for the given split key. */
593
+ has(key: string): boolean;
594
+ /** Number of rating splits. */
595
+ get size(): number;
596
+ /** All split keys the player has ratings for. */
597
+ keys(): IterableIterator<string>;
598
+ /** All rating splits the player has. */
599
+ values(): IterableIterator<RatingSplitWire>;
600
+ /** `[key, split]` pairs for every rating split. */
601
+ entries(): IterableIterator<[string, RatingSplitWire]>;
602
+ /**
603
+ * `for (const [key, split] of sportRating) { ... }` — iterate every
604
+ * rating split the player has in this sport.
605
+ */
606
+ [Symbol.iterator](): IterableIterator<[string, RatingSplitWire]>;
607
+ }
608
+
581
609
  /**
582
- * Check if a scope is valid.
583
- *
584
- * @param scope - Scope string to validate
585
- * @returns True if scope is valid
610
+ * {@link Member} — partner-facing player record.
586
611
  *
587
- * @category OAuth
612
+ * @module
588
613
  */
589
- declare function validateScope(scope: string): scope is OAuthScope;
614
+
590
615
  /**
591
- * Get a human-readable description of a scope.
616
+ * Map-like wrapper around a player's sport → {@link SportRating}.
592
617
  *
593
- * @param scope - Scope string
594
- * @returns Description of what the scope grants access to
618
+ * Supports `.get(code)`, `.has(code)`, `.size`, and iteration so the
619
+ * shape feels native:
595
620
  *
596
- * @category OAuth
621
+ * ```ts
622
+ * member.sport.get('pickleball')?.rating
623
+ * for (const [code, rating] of member.sport) { ... }
624
+ * ```
625
+ *
626
+ * @category Members
597
627
  */
598
- declare function describeScope(scope: OAuthScope): string;
628
+ declare class MemberSportMap {
629
+ #private;
630
+ constructor(wire: Readonly<Record<string, SportRatingWire>> | undefined);
631
+ get(sport: string): SportRating | undefined;
632
+ has(sport: string): boolean;
633
+ get size(): number;
634
+ keys(): IterableIterator<string>;
635
+ values(): IterableIterator<SportRating>;
636
+ entries(): IterableIterator<[string, SportRating]>;
637
+ [Symbol.iterator](): IterableIterator<[string, SportRating]>;
638
+ }
599
639
  /**
600
- * Get descriptions for multiple scopes.
640
+ * A partner-facing player record.
601
641
  *
602
- * @param scopes - List of scope strings
603
- * @returns Array of objects with scope and description
642
+ * Returned by {@link MembersResource.get} (full detail, requires an
643
+ * active OAuth connection) and {@link MembersResource.search} (limited
644
+ * detail for public search).
604
645
  *
605
- * @category OAuth
646
+ * Rating data lives under {@link sport} — keyed by sport code. The
647
+ * backend returns only the sports the player has ratings in, or only
648
+ * the sports requested via the `sport=` query filter. Use
649
+ * {@link ratingFor} to fetch the primary rating for a specific sport
650
+ * with a sensible default.
651
+ *
652
+ * @category Members
606
653
  */
607
- declare function describeScopes(scopes: OAuthScope[]): Array<{
608
- scope: OAuthScope;
609
- description: string;
610
- }>;
654
+ declare class Member {
655
+ readonly memberId: number;
656
+ readonly id: string | null;
657
+ readonly firstName: string;
658
+ readonly lastName: string;
659
+ readonly fullName: string;
660
+ readonly displayName: string;
661
+ readonly age: number | null;
662
+ readonly city: string | null;
663
+ readonly state: string | null;
664
+ readonly zip: string | null;
665
+ readonly country: string | null;
666
+ readonly gender: Gender | null;
667
+ readonly status: MemberStatusWire;
668
+ readonly sport: MemberSportMap;
669
+ readonly activeLeagues: readonly string[] | null;
670
+ readonly email: string | null;
671
+ readonly grantedScopes: readonly string[] | null;
672
+ constructor(wire: PartnerMemberWire);
673
+ /** Full name — alias for {@link fullName}, matching common usage. */
674
+ get name(): string;
675
+ /** The list of sport codes this player has ratings in. */
676
+ get sports(): readonly string[];
677
+ /**
678
+ * Primary rating for a given sport.
679
+ *
680
+ * @param sport Sport code — defaults to `'pickleball'`.
681
+ * @returns The primary rating value, or `null` if the player has no
682
+ * ratings for that sport.
683
+ */
684
+ ratingFor(sport?: string): number | null;
685
+ /**
686
+ * Get a specific rating split for a sport.
687
+ *
688
+ * @param key Split key (e.g. `'overall-open'`).
689
+ * @param sport Sport code — defaults to `'pickleball'`.
690
+ */
691
+ split(key: string, sport?: string): RatingSplitWire | null;
692
+ /** Compact summary for console output. */
693
+ toString(): string;
694
+ }
695
+
611
696
  /**
612
- * Generate a random state parameter for CSRF protection.
613
- *
614
- * @returns Random 32-character hexadecimal string
697
+ * {@link RatingUpdate} — a single rating change notification.
615
698
  *
616
- * @category OAuth
699
+ * @module
617
700
  */
618
- declare function generateState(): string;
619
701
 
620
702
  /**
621
- * Vairified SDK Client
703
+ * A single rating change notification.
622
704
  *
623
- * Main client for the Vairified Partner API.
705
+ * Returned by {@link MembersResource.ratingUpdates} (polling) and
706
+ * delivered via webhook callbacks to partners that have registered a
707
+ * webhook URL and have subscribers.
624
708
  *
625
- * @module
709
+ * @category Members
626
710
  */
711
+ declare class RatingUpdate {
712
+ readonly memberId: number;
713
+ readonly id: string | null;
714
+ readonly displayName: string | null;
715
+ readonly sport: string | null;
716
+ readonly previousRating: number | null;
717
+ readonly newRating: number | null;
718
+ readonly changedAt: string | null;
719
+ readonly ratingSplits: Readonly<Record<string, RatingSplitWire>> | null;
720
+ constructor(wire: PartnerRatingUpdateWire);
721
+ /**
722
+ * Rating change amount — `newRating - previousRating`. Returns `null`
723
+ * if either rating is missing from the update payload.
724
+ */
725
+ get delta(): number | null;
726
+ /** `true` when the new rating is strictly higher than the previous. */
727
+ get improved(): boolean;
728
+ toString(): string;
729
+ }
627
730
 
628
- declare const ENVIRONMENTS: {
629
- readonly production: "https://api-next.vairified.com/api/v1";
630
- readonly staging: "https://api-staging.vairified.com/api/v1";
631
- readonly local: "http://localhost:3001/api/v1";
632
- };
633
731
  /**
634
- * Available environment presets for the Vairified client.
732
+ * {@link MembersResource} — member lookups, search, and rating updates.
635
733
  *
636
- * @category Client
734
+ * @module
637
735
  */
638
- type VairifiedEnvironment = keyof typeof ENVIRONMENTS;
736
+
639
737
  /**
640
- * Client for the Vairified Partner API.
641
- *
642
- * @category Client
643
- *
644
- * @example
645
- * ```ts
646
- * const client = new Vairified({ apiKey: 'vair_pk_xxx' });
647
- *
648
- * // Get a member
649
- * const member = await client.getMember('user_123');
650
- * console.log(member.name, member.rating);
651
- *
652
- * // Search for players
653
- * const results = await client.search({ city: 'Austin', ratingMin: 4.0 });
654
- * for (const player of results) {
655
- * console.log(player.name, player.rating);
656
- * }
657
- *
658
- * // Submit a match (doubles: 11-9, 11-7)
659
- * const match = new Match({
660
- * event: 'Weekly League',
661
- * bracket: '4.0 Doubles',
662
- * date: new Date(),
663
- * team1: ['p1', 'p2'],
664
- * team2: ['p3', 'p4'],
665
- * scores: [[11, 9], [11, 7]],
666
- * });
667
- * const result = await client.submitMatch(match);
668
- * if (result.ok) {
669
- * console.log(`Submitted ${result.numGames} games`);
670
- * }
671
- * ```
738
+ * Member operations — get a single member, auto-paginating search,
739
+ * find by name, and polling for rating change notifications.
672
740
  *
673
- * @remarks
674
- * If your API key has the "dry-run" scope, match submissions will be
675
- * validated but not persisted. This is useful for testing integrations.
741
+ * @category Resources
676
742
  */
677
- declare class Vairified {
678
- /** API key */
679
- readonly apiKey: string;
680
- /** Base URL */
681
- readonly baseUrl: string;
682
- /** Environment name */
683
- readonly env: VairifiedEnvironment;
684
- /** Request timeout in ms */
685
- readonly timeout: number;
686
- constructor(options?: VairifiedOptions);
687
- private getEnvVar;
688
- private getEnvApiKey;
689
- private getHeaders;
690
- private handleError;
691
- private request;
743
+ declare class MembersResource {
744
+ #private;
745
+ /** @internal */
746
+ constructor(http: HttpTransport);
692
747
  /**
693
- * Get a connected member by their external ID.
748
+ * Get a connected member by external ID.
694
749
  *
695
- * **Requires OAuth Connection**: The player must have connected their
696
- * account to your application via OAuth before you can access their data.
750
+ * **Requires an active OAuth connection** between your partner app
751
+ * and the player. Use the OAuth flow on `client.oauth` first.
697
752
  *
698
- * @param playerId - External player ID (vair_mem_xxx format)
699
- * @returns Member object with profile and rating data
700
- * @throws NotFoundError if member is not found or invalid ID format
701
- * @throws ForbiddenError if player has not connected to your app
753
+ * @param playerId External player ID in `vair_mem_xxx` format.
754
+ * @param options.sport Optional sport filter — single code or list.
755
+ * When omitted, the response contains every sport the player has
756
+ * ratings in.
757
+ * @throws {@link NotFoundError} if the external ID is unknown.
758
+ * @throws {@link VairifiedError} if the player has not connected to
759
+ * your app (403) or the request otherwise fails.
702
760
  *
703
761
  * @example
704
762
  * ```ts
705
- * const member = await client.getMember('vair_mem_0ABC123def456GHI789jk');
706
- * console.log(member.name, member.rating);
707
- * console.log(member.ratingSplits.open); // Open division rating
708
- * console.log(member.grantedScopes); // ['profile:read', 'rating:read']
709
- * ```
710
- */
711
- getMember(playerId: string): Promise<Member>;
712
- /**
713
- * Search for players.
763
+ * const member = await client.members.get('vair_mem_xxx');
764
+ * console.log(member.name, member.ratingFor('pickleball'));
714
765
  *
715
- * @param filters - Search filters
716
- * @returns SearchResults with players and pagination
766
+ * // Just pickleball
767
+ * const member2 = await client.members.get('vair_mem_xxx', { sport: 'pickleball' });
717
768
  *
718
- * @example
719
- * ```ts
720
- * const results = await client.search({
721
- * city: 'Austin',
722
- * ratingMin: 4.0,
723
- * vairifiedOnly: true,
769
+ * // Multiple sports
770
+ * const member3 = await client.members.get('vair_mem_xxx', {
771
+ * sport: ['pickleball', 'padel'],
724
772
  * });
725
- *
726
- * for (const player of results) {
727
- * console.log(player.name, player.rating);
728
- * }
729
- *
730
- * // Pagination
731
- * if (results.hasMore) {
732
- * const nextPage = await results.nextPage();
733
- * }
734
773
  * ```
735
774
  */
736
- search(filters?: SearchFilters): Promise<SearchResults>;
775
+ get(playerId: string, options?: {
776
+ sport?: string | readonly string[];
777
+ }): Promise<Member>;
737
778
  /**
738
- * Find a single player by name.
779
+ * Search for members, yielding each match as a {@link Member}.
739
780
  *
740
- * @param name - Player name to search for
741
- * @returns Player if found, undefined otherwise
781
+ * This is an **auto-paginating async iterator** — it fetches pages
782
+ * from the server lazily as you iterate, so you can stream through
783
+ * thousands of results without holding them all in memory:
742
784
  *
743
- * @example
744
785
  * ```ts
745
- * const player = await client.findPlayer('John Smith');
746
- * if (player) {
747
- * console.log(player.rating);
786
+ * for await (const m of client.members.search({ city: 'Austin' })) {
787
+ * console.log(m.name, m.ratingFor('pickleball'));
748
788
  * }
749
789
  * ```
750
- */
751
- findPlayer(name: string): Promise<Player | undefined>;
752
- /**
753
- * Submit a single match.
754
- *
755
- * @param match - Match object with teams and scores
756
- * @returns MatchResult with submission status
757
790
  *
758
- * @example
759
- * ```ts
760
- * const match = new Match({
761
- * event: 'Weekly League',
762
- * bracket: '4.0 Doubles',
763
- * date: new Date(),
764
- * team1: ['p1', 'p2'],
765
- * team2: ['p3', 'p4'],
766
- * scores: [[11, 9], [11, 7]],
767
- * });
768
- *
769
- * const result = await client.submitMatch(match);
770
- * if (result.ok) {
771
- * console.log(`Submitted ${result.numGames} games`);
772
- * }
773
- * ```
791
+ * Stop early by `break`-ing out of the loop, or cap the total with
792
+ * `maxResults`.
774
793
  */
775
- submitMatch(match: Match): Promise<MatchResult>;
794
+ search(filters?: SearchFilters): AsyncGenerator<Member, void, void>;
776
795
  /**
777
- * Submit multiple matches in a batch.
796
+ * Return the first search hit for a name, or `null`.
778
797
  *
779
- * @param matches - List of Match objects
780
- * @returns MatchResult with submission status
798
+ * Convenience for the common "look up by name" case:
781
799
  *
782
- * @example
783
800
  * ```ts
784
- * const result = await client.submitMatches([match1, match2, match3]);
785
- * console.log(`Submitted ${result.numGames} games from ${result.numMatches} matches`);
786
- *
787
- * if (result.dryRun) {
788
- * console.log('This was a dry run - no data persisted');
801
+ * const mike = await client.members.find('Mike Barker');
802
+ * if (mike) {
803
+ * console.log(mike.ratingFor('pickleball'));
789
804
  * }
790
805
  * ```
791
806
  */
792
- submitMatches(matches: Match[]): Promise<MatchResult>;
807
+ find(name: string): Promise<Member | null>;
793
808
  /**
794
- * Get rating updates for subscribed members.
809
+ * Fetch up to 100 members by their member IDs in one call.
795
810
  *
796
- * Members are subscribed when you call getMember().
811
+ * Unknown IDs are silently omitted — the returned array may be
812
+ * shorter than the input. Results are returned in the same order
813
+ * as the input IDs.
797
814
  *
798
- * @returns List of RatingUpdate objects
815
+ * @param ids - Array of integer member IDs (max 100).
816
+ * @param options - Optional filters.
817
+ * @param options.sport - Sport code to scope ratings (e.g. `'pickleball'`).
818
+ * @returns Array of {@link Member} instances.
819
+ * @throws {@link ValidationError} If more than 100 IDs are provided.
820
+ * @category Members
799
821
  *
800
822
  * @example
801
823
  * ```ts
802
- * const updates = await client.getRatingUpdates();
803
- * for (const update of updates) {
804
- * console.log(`${update.memberId}: ${update.previousRating} → ${update.newRating}`);
805
- * if (update.improved) {
806
- * const member = await update.getMember();
807
- * console.log(`${member.name} improved!`);
808
- * }
824
+ * const members = await client.members.getBulk([4873327, 4873328]);
825
+ * for (const m of members) {
826
+ * console.log(m.name, m.ratingFor('pickleball'));
809
827
  * }
810
828
  * ```
811
829
  */
812
- getRatingUpdates(): Promise<RatingUpdate[]>;
830
+ getBulk(ids: number[], options?: {
831
+ sport?: string;
832
+ }): Promise<Member[]>;
813
833
  /**
814
- * Test webhook endpoint.
834
+ * Poll for rating change notifications.
815
835
  *
816
- * @param webhookUrl - URL to send test webhook to
817
- * @returns Test result
836
+ * Returns a list of {@link RatingUpdate} objects for every player
837
+ * whose rating has changed since the last poll. Members are
838
+ * considered subscribed when they have an active OAuth connection
839
+ * with the `user:webhook:subscribe` scope.
818
840
  */
819
- testWebhook(webhookUrl: string): Promise<Record<string, unknown>>;
841
+ ratingUpdates(): Promise<readonly RatingUpdate[]>;
842
+ }
843
+
844
+ /**
845
+ * Vairified OAuth helpers and type definitions.
846
+ *
847
+ * Use the {@link OAuthResource} on `client.oauth` for the full flow —
848
+ * these helpers exist for partners who need to build authorization URLs
849
+ * or validate scopes outside the client (e.g. in a frontend that only
850
+ * handles the redirect step).
851
+ *
852
+ * @module
853
+ */
854
+ /**
855
+ * Every OAuth scope the Vairified authorization server accepts.
856
+ *
857
+ * Declaring this as a string union (rather than a free-form `string`)
858
+ * lets TypeScript catch typos at authoring time:
859
+ *
860
+ * ```ts
861
+ * const scopes: OAuthScope[] = ['user:profile:read', 'user:rating:read']; // ok
862
+ * const bad: OAuthScope[] = ['user:profile:read', 'rating']; // type error
863
+ * ```
864
+ *
865
+ * @category OAuth
866
+ */
867
+ type OAuthScope = 'user:profile:read' | 'user:profile:email' | 'user:rating:read' | 'user:rating:history' | 'user:match:submit' | 'user:webhook:subscribe';
868
+ /**
869
+ * Human-readable description for every OAuth scope.
870
+ *
871
+ * @category OAuth
872
+ */
873
+ declare const SCOPES: Readonly<Record<OAuthScope, string>>;
874
+ /**
875
+ * The scopes automatically requested when none are specified.
876
+ *
877
+ * @category OAuth
878
+ */
879
+ declare const DEFAULT_SCOPES: readonly OAuthScope[];
880
+ /**
881
+ * Configuration for building an authorization URL manually.
882
+ *
883
+ * @category OAuth
884
+ */
885
+ interface OAuthConfig {
886
+ readonly apiKey: string;
887
+ readonly redirectUri: string;
888
+ readonly baseUrl?: string;
889
+ }
890
+ /**
891
+ * Response from starting an OAuth authorization flow.
892
+ *
893
+ * @category OAuth
894
+ */
895
+ interface AuthorizationResponse {
896
+ readonly authorizationUrl: string;
897
+ readonly code: string;
898
+ readonly state?: string;
899
+ }
900
+ /**
901
+ * Response from exchanging an authorization code for tokens.
902
+ *
903
+ * @category OAuth
904
+ */
905
+ interface TokenResponse {
906
+ readonly accessToken: string;
907
+ readonly refreshToken: string | null;
908
+ readonly expiresIn: number;
909
+ readonly scope: readonly string[];
910
+ readonly playerId: string;
911
+ }
912
+ /**
913
+ * Build the URL to redirect users to for OAuth authorization.
914
+ *
915
+ * Prefer {@link OAuthResource.authorize} on a Vairified client — this
916
+ * helper is only useful when you need to construct the URL without
917
+ * making an HTTP call first (for example, in a pure-frontend handoff).
918
+ *
919
+ * @category OAuth
920
+ */
921
+ declare function getAuthorizationUrl(config: OAuthConfig, options?: {
922
+ scopes?: readonly OAuthScope[];
923
+ state?: string;
924
+ }): string;
925
+ /**
926
+ * Check whether a scope string is one the Vairified server accepts.
927
+ *
928
+ * @category OAuth
929
+ */
930
+ declare function validateScope(scope: string): scope is OAuthScope;
931
+ /**
932
+ * Get a human-readable description of an OAuth scope.
933
+ *
934
+ * Returns `"Unknown scope: {scope}"` for unrecognized scopes so this
935
+ * function is safe to call on user-supplied input.
936
+ *
937
+ * @category OAuth
938
+ */
939
+ declare function describeScope(scope: string): string;
940
+ /**
941
+ * Describe multiple scopes at once.
942
+ *
943
+ * @category OAuth
944
+ */
945
+ declare function describeScopes(scopes: readonly string[]): readonly {
946
+ scope: string;
947
+ description: string;
948
+ }[];
949
+ /**
950
+ * Generate a cryptographically-random CSRF state token suitable for
951
+ * use with {@link OAuthResource.authorize}.
952
+ *
953
+ * Uses the Web Crypto API (available in Node 19+ and all modern
954
+ * browsers). The returned string is URL-safe base64 of 32 random bytes.
955
+ *
956
+ * @category OAuth
957
+ */
958
+ declare function generateState(): string;
959
+
960
+ /**
961
+ * {@link OAuthResource} — OAuth 2.0 flow for player consent.
962
+ *
963
+ * @module
964
+ */
965
+
966
+ /**
967
+ * OAuth 2.0 flow for obtaining player consent.
968
+ *
969
+ * Typical flow:
970
+ *
971
+ * 1. {@link authorize} — start an authorization, get a URL to redirect
972
+ * the player to.
973
+ * 2. Player approves on the Vairified site and is redirected back to
974
+ * your `redirectUri` with a `code` query parameter.
975
+ * 3. {@link exchangeToken} — swap the code for access and refresh
976
+ * tokens plus the player's external ID.
977
+ * 4. Store the refresh token and call {@link refresh} when the access
978
+ * token expires.
979
+ * 5. {@link revoke} — disconnect a player from your app.
980
+ *
981
+ * @category Resources
982
+ */
983
+ declare class OAuthResource {
984
+ #private;
985
+ /** @internal */
986
+ constructor(http: HttpTransport);
820
987
  /**
821
988
  * Start an OAuth authorization flow.
822
989
  *
823
- * This creates a pending authorization and returns the URL where
824
- * users should be redirected to approve access.
825
- *
826
- * @param redirectUri - Your application's callback URL
827
- * @param scopes - Permission scopes to request (defaults to profile:read, rating:read)
828
- * @param state - CSRF protection state parameter (recommended)
829
- * @returns AuthorizationResponse with the URL to redirect users to
830
- * @throws OAuthError if the authorization fails to start
831
- *
832
- * @example
833
- * ```ts
834
- * const auth = await client.startOAuth(
835
- * 'https://myapp.com/callback',
836
- * ['profile:read', 'rating:read', 'match:submit'],
837
- * 'random_csrf_token',
838
- * );
839
- * // Redirect user to auth.authorizationUrl
840
- * window.location.href = auth.authorizationUrl;
841
- * ```
842
- *
843
- * @category OAuth
844
- */
845
- startOAuth(redirectUri: string, scopes?: OAuthScope[], state?: string): Promise<AuthorizationResponse>;
846
- /**
847
- * Exchange an authorization code for access and refresh tokens.
848
- *
849
- * Call this after the user approves access and is redirected back
850
- * to your application with a code parameter.
851
- *
852
- * @param code - Authorization code from the callback URL
853
- * @param redirectUri - Must match the redirectUri used in startOAuth
854
- * @returns TokenResponse with access_token, refresh_token, and player_id
855
- * @throws OAuthError if the code is invalid or expired
856
- *
857
- * @example
858
- * ```ts
859
- * // After user is redirected to: https://myapp.com/callback?code=xxx
860
- * const tokens = await client.exchangeToken(
861
- * new URL(window.location.href).searchParams.get('code')!,
862
- * 'https://myapp.com/callback',
863
- * );
864
- * // Store tokens.accessToken and tokens.refreshToken securely
865
- * // Use tokens.playerId to identify the connected player
866
- * ```
867
- *
868
- * @category OAuth
990
+ * @throws {@link OAuthError} with `errorCode: 'invalid_scope'` if a
991
+ * requested scope is not in the accepted list.
869
992
  */
870
- exchangeToken(code: string, redirectUri: string): Promise<TokenResponse>;
993
+ authorize(options: {
994
+ redirectUri: string;
995
+ scopes?: readonly OAuthScope[];
996
+ state?: string;
997
+ }): Promise<AuthorizationResponse>;
998
+ /** Exchange an authorization code for access and refresh tokens. */
999
+ exchangeToken(options: {
1000
+ code: string;
1001
+ redirectUri: string;
1002
+ }): Promise<TokenResponse>;
1003
+ /** Refresh an expired access token using a refresh token. */
1004
+ refresh(refreshToken: string): Promise<TokenResponse>;
1005
+ /** Revoke a player's OAuth connection to your app. */
1006
+ revoke(playerId: string): Promise<Record<string, unknown>>;
1007
+ /** Return the list of OAuth scopes the server currently supports. */
1008
+ availableScopes(): Promise<readonly {
1009
+ name: string;
1010
+ description: string;
1011
+ }[]>;
1012
+ }
1013
+
1014
+ /**
1015
+ * {@link WebhookDelivery} and {@link WebhookDeliveriesResult} — webhook
1016
+ * delivery inspection models.
1017
+ *
1018
+ * @module
1019
+ */
1020
+
1021
+ /**
1022
+ * A single webhook delivery attempt.
1023
+ *
1024
+ * @category Webhooks
1025
+ */
1026
+ declare class WebhookDelivery {
1027
+ readonly id: string;
1028
+ readonly event: string;
1029
+ readonly url: string;
1030
+ readonly statusCode: number | null;
1031
+ readonly responseBody: string | null;
1032
+ readonly errorMessage: string | null;
1033
+ readonly attempts: number;
1034
+ readonly maxAttempts: number;
1035
+ readonly lastAttemptAt: string;
1036
+ readonly nextRetryAt: string | null;
1037
+ readonly completedAt: string | null;
1038
+ readonly createdAt: string;
1039
+ readonly payload: Readonly<Record<string, unknown>>;
1040
+ /** @internal */
1041
+ constructor(wire: WebhookDeliveryWire);
1042
+ /** Whether delivery completed successfully (2xx status). */
1043
+ get succeeded(): boolean;
1044
+ /** Whether delivery failed definitively (completed with non-2xx). */
1045
+ get failed(): boolean;
1046
+ }
1047
+ /**
1048
+ * Paginated list of webhook delivery attempts.
1049
+ *
1050
+ * @category Webhooks
1051
+ */
1052
+ declare class WebhookDeliveriesResult {
1053
+ readonly deliveries: readonly WebhookDelivery[];
1054
+ readonly total: number;
1055
+ /** @internal */
1056
+ constructor(wire: WebhookDeliveriesResultWire);
1057
+ }
1058
+
1059
+ /**
1060
+ * {@link WebhooksResource} — webhook delivery inspection.
1061
+ *
1062
+ * @module
1063
+ */
1064
+
1065
+ /**
1066
+ * Webhook delivery inspection.
1067
+ *
1068
+ * @category Resources
1069
+ */
1070
+ declare class WebhooksResource {
1071
+ #private;
1072
+ /** @internal */
1073
+ constructor(http: HttpTransport);
871
1074
  /**
872
- * Refresh an expired access token.
873
- *
874
- * Use this when an access token expires to obtain a new one
875
- * without requiring the user to re-authorize.
1075
+ * List recent webhook delivery attempts.
876
1076
  *
877
- * @param refreshToken - The refresh token from a previous token exchange
878
- * @returns TokenResponse with new access_token and optionally a new refresh_token
879
- * @throws OAuthError if the refresh token is invalid or revoked
1077
+ * @param options - Optional filters and pagination.
1078
+ * @param options.event - Filter by event type (e.g. `'rating.updated'`).
1079
+ * @param options.status - Filter: `'all'`, `'pending'`, `'success'`, or `'failed'`.
1080
+ * @param options.limit - Results per page (1-100, default 20).
1081
+ * @param options.offset - Pagination offset.
1082
+ * @returns {@link WebhookDeliveriesResult} with entries and total.
1083
+ * @category Webhooks
880
1084
  *
881
1085
  * @example
882
1086
  * ```ts
883
- * try {
884
- * const newTokens = await client.refreshAccessToken(storedRefreshToken);
885
- * // Update stored tokens
886
- * } catch (e) {
887
- * if (e instanceof OAuthError && e.errorCode === 'invalid_grant') {
888
- * // Refresh token revoked, user needs to re-authorize
889
- * }
1087
+ * const result = await client.webhooks.deliveries({ status: 'failed' });
1088
+ * for (const d of result.deliveries) {
1089
+ * console.log(d.event, d.statusCode, d.errorMessage);
890
1090
  * }
891
1091
  * ```
892
- *
893
- * @category OAuth
894
1092
  */
895
- refreshAccessToken(refreshToken: string): Promise<TokenResponse>;
1093
+ deliveries(options?: {
1094
+ event?: string;
1095
+ status?: 'all' | 'pending' | 'success' | 'failed';
1096
+ limit?: number;
1097
+ offset?: number;
1098
+ }): Promise<WebhookDeliveriesResult>;
1099
+ }
1100
+
1101
+ /**
1102
+ * {@link Vairified} — the main entry point of the SDK.
1103
+ *
1104
+ * @module
1105
+ */
1106
+
1107
+ /**
1108
+ * Environment preset → base URL mapping.
1109
+ *
1110
+ * Partners can switch between production, staging, and local development
1111
+ * without memorizing hostnames.
1112
+ *
1113
+ * @category Client
1114
+ */
1115
+ declare const ENVIRONMENTS: Readonly<Record<VairifiedEnvironment, string>>;
1116
+ /**
1117
+ * Async client for the Vairified Partner API.
1118
+ *
1119
+ * The client is organized around sub-resources that mirror the REST
1120
+ * structure — {@link members}, {@link matches}, {@link oauth},
1121
+ * {@link leaderboard}. Each sub-resource is a thin wrapper around the
1122
+ * HTTP transport on this object.
1123
+ *
1124
+ * ## Lifecycle
1125
+ *
1126
+ * The client holds no persistent connections itself — it's safe to
1127
+ * create one per request if you want. But for typical usage, wrap it
1128
+ * in `await using` so resources are cleaned up deterministically:
1129
+ *
1130
+ * ```ts
1131
+ * await using client = new Vairified({ apiKey: 'vair_pk_xxx' });
1132
+ *
1133
+ * const member = await client.members.get('vair_mem_xxx');
1134
+ * console.log(member.name, member.ratingFor('pickleball'));
1135
+ *
1136
+ * for await (const m of client.members.search({ city: 'Austin' })) {
1137
+ * console.log(m.name);
1138
+ * }
1139
+ * ```
1140
+ *
1141
+ * `await using` requires TypeScript 5.2+ and Node 20+. If you can't
1142
+ * use it, just call `await client.close()` manually when you're done.
1143
+ *
1144
+ * @category Client
1145
+ */
1146
+ declare class Vairified {
1147
+ #private;
1148
+ /** The resolved API key this client is using. */
1149
+ readonly apiKey: string;
1150
+ /** The resolved base URL (production, staging, local, or custom). */
1151
+ readonly baseUrl: string;
1152
+ /** The resolved environment name. */
1153
+ readonly env: VairifiedEnvironment;
1154
+ /** Request timeout in milliseconds. */
1155
+ readonly timeoutMs: number;
1156
+ /** Member operations — get, search, find, ratingUpdates. */
1157
+ readonly members: MembersResource;
1158
+ /** Match submission — submit, testWebhook. */
1159
+ readonly matches: MatchesResource;
1160
+ /** OAuth flow — authorize, exchangeToken, refresh, revoke. */
1161
+ readonly oauth: OAuthResource;
1162
+ /** Leaderboard queries — list, rank, categories. */
1163
+ readonly leaderboard: LeaderboardResource;
1164
+ /** Webhook delivery inspection — deliveries. */
1165
+ readonly webhooks: WebhooksResource;
1166
+ constructor(options?: VairifiedOptions);
896
1167
  /**
897
- * Revoke a player's OAuth connection.
898
- *
899
- * This disconnects the player from your application. You will no
900
- * longer be able to access their data or submit matches on their behalf.
1168
+ * API usage statistics for the current API key.
901
1169
  *
902
- * @param playerId - The player's external ID (vair_mem_xxx format)
903
- * @throws OAuthError if the revocation fails
904
- *
905
- * @example
906
- * ```ts
907
- * await client.revokeConnection('vair_mem_0ABC123def456GHI789jk');
908
- * // Player is now disconnected
909
- * ```
910
- *
911
- * @category OAuth
1170
+ * Returns rate-limit status, request counts, and quota usage.
912
1171
  */
913
- revokeConnection(playerId: string): Promise<void>;
1172
+ usage(): Promise<Record<string, unknown>>;
914
1173
  /**
915
- * Get a list of available OAuth scopes.
916
- *
917
- * @returns List of scope objects with id, name, and description
918
- *
919
- * @example
920
- * ```ts
921
- * const scopes = await client.getAvailableScopes();
922
- * for (const scope of scopes) {
923
- * console.log(`${scope.id}: ${scope.description}`);
924
- * }
925
- * ```
1174
+ * Release any resources held by the client.
926
1175
  *
927
- * @category OAuth
1176
+ * The current transport is stateless, so this is a no-op today, but
1177
+ * partners should still call it (or use `await using`) so the SDK
1178
+ * can add connection pooling later without breaking them.
928
1179
  */
929
- getAvailableScopes(): Promise<Array<{
930
- id: string;
931
- name: string;
932
- description: string;
933
- }>>;
1180
+ close(): Promise<void>;
934
1181
  /**
935
- * Get API usage statistics for your partner account.
936
- *
937
- * @returns Usage statistics (requests, limits, etc.)
938
- *
939
- * @example
940
- * ```ts
941
- * const usage = await client.getUsage();
942
- * console.log(`Requests today: ${usage.requestsToday}`);
943
- * console.log(`Rate limit: ${usage.rateLimit}/hour`);
944
- * ```
945
- *
946
- * @category Client
1182
+ * Explicit resource management hook — enables
1183
+ * `await using client = new Vairified({ ... })` (TypeScript 5.2+).
947
1184
  */
948
- getUsage(): Promise<Record<string, unknown>>;
1185
+ [Symbol.asyncDispose](): Promise<void>;
1186
+ /** Compact summary for console output. */
1187
+ toString(): string;
949
1188
  }
950
1189
 
951
1190
  /**
952
- * Vairified SDK Errors
1191
+ * Vairified SDK — error hierarchy.
1192
+ *
1193
+ * All SDK errors inherit from {@link VairifiedError}, so a single
1194
+ * `catch (err: unknown) { if (err instanceof VairifiedError) ... }`
1195
+ * covers everything. HTTP-status-specific subclasses (auth, not found,
1196
+ * rate limit, validation) are thrown automatically by the HTTP layer.
953
1197
  *
954
1198
  * @module
955
1199
  */
956
1200
  /**
957
- * Base error class for Vairified SDK errors.
1201
+ * Base class for every error thrown by the Vairified SDK.
958
1202
  *
959
1203
  * @category Errors
960
1204
  */
961
1205
  declare class VairifiedError extends Error {
962
- /** HTTP status code */
963
- statusCode?: number;
964
- /** Response body */
965
- response?: unknown;
1206
+ /** HTTP status code (if the error came from an API response). */
1207
+ readonly statusCode?: number;
1208
+ /** Raw response body parsed as JSON when available. */
1209
+ readonly response?: unknown;
966
1210
  constructor(message: string, statusCode?: number, response?: unknown);
967
1211
  }
968
1212
  /**
969
- * Error thrown when API rate limit is exceeded.
1213
+ * Thrown on HTTP 429 responses. Carries the `Retry-After` header value
1214
+ * when the server provides one.
970
1215
  *
971
1216
  * @category Errors
972
1217
  */
973
1218
  declare class RateLimitError extends VairifiedError {
974
- /** Seconds to wait before retrying */
975
- retryAfter?: number;
1219
+ /** Seconds to wait before retrying, or `undefined` if the server didn't say. */
1220
+ readonly retryAfter?: number;
976
1221
  constructor(message?: string, retryAfter?: number, response?: unknown);
977
1222
  }
978
1223
  /**
979
- * Error thrown when API key is invalid or missing.
1224
+ * Thrown on HTTP 401 — invalid or missing API key.
980
1225
  *
981
1226
  * @category Errors
982
1227
  */
@@ -984,7 +1229,7 @@ declare class AuthenticationError extends VairifiedError {
984
1229
  constructor(message?: string, response?: unknown);
985
1230
  }
986
1231
  /**
987
- * Error thrown when a requested resource is not found.
1232
+ * Thrown on HTTP 404 — the resource doesn't exist.
988
1233
  *
989
1234
  * @category Errors
990
1235
  */
@@ -992,7 +1237,9 @@ declare class NotFoundError extends VairifiedError {
992
1237
  constructor(message?: string, response?: unknown);
993
1238
  }
994
1239
  /**
995
- * Error thrown when request validation fails.
1240
+ * Thrown on HTTP 400 — the server rejected the request payload.
1241
+ *
1242
+ * Inspect {@link VairifiedError.response} for field-level details.
996
1243
  *
997
1244
  * @category Errors
998
1245
  */
@@ -1000,16 +1247,18 @@ declare class ValidationError extends VairifiedError {
1000
1247
  constructor(message?: string, response?: unknown);
1001
1248
  }
1002
1249
  /**
1003
- * Error thrown when an OAuth operation fails.
1004
- *
1005
- * This can occur during authorization, token exchange, refresh, or revocation.
1250
+ * Thrown by {@link OAuthResource} methods when authorization, token
1251
+ * exchange, refresh, or revocation fails.
1006
1252
  *
1007
1253
  * @category Errors
1008
1254
  */
1009
1255
  declare class OAuthError extends VairifiedError {
1010
- /** OAuth error code (e.g., 'invalid_grant', 'expired_token') */
1011
- errorCode?: string;
1256
+ /**
1257
+ * OAuth error code such as `'invalid_grant'`, `'invalid_scope'`,
1258
+ * or `'expired_token'`. Check this to branch on the specific failure.
1259
+ */
1260
+ readonly errorCode?: string;
1012
1261
  constructor(message?: string, errorCode?: string, response?: unknown);
1013
1262
  }
1014
1263
 
1015
- export { AuthenticationError, type AuthorizationResponse, DEFAULT_SCOPES, Match, type MatchApiData, type MatchInput, MatchResult, type MatchResultData, Member, type MemberData, NotFoundError, type OAuthConfig, OAuthError, type OAuthScope, Player, type PlayerSearchData, RateLimitError, RatingSplit, type RatingSplitData, RatingSplits, type RatingSplitsData, RatingUpdate, type RatingUpdateData, SCOPES, type SearchFilters, SearchResults, type SearchResultsData, type TokenResponse, Vairified, type VairifiedEnvironment, VairifiedError, type VairifiedOptions, ValidationError, describeScope, describeScopes, generateState, getAuthorizationUrl, validateScope };
1264
+ export { type ApiErrorResponse, AuthenticationError, type AuthorizationResponse, DEFAULT_SCOPES, ENVIRONMENTS, type GameInput, type Gender, type LeaderboardOptions, LeaderboardResource, type MatchBatch, MatchBatchResult, type MatchBatchResultWire, type MatchInput, MatchesResource, Member, MemberSportMap, type MemberStatusWire, MembersResource, NotFoundError, type OAuthConfig, OAuthError, OAuthResource, type OAuthScope, type PartnerMemberWire, type PartnerRatingUpdateWire, type PlayerRankOptions, RateLimitError, type RatingSplitWire, RatingUpdate, SCOPES, type SearchFilters, SportRating, type SportRatingWire, type TokenResponse, TournamentImportResult, type TournamentImportResultWire, Vairified, type VairifiedEnvironment, VairifiedError, type VairifiedOptions, ValidationError, WebhookDeliveriesResult, type WebhookDeliveriesResultWire, WebhookDelivery, type WebhookDeliveryWire, WebhooksResource, describeScope, describeScopes, generateState, getAuthorizationUrl, validateScope };