vairified 0.4.0 → 0.6.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
@@ -157,6 +157,7 @@ interface PartnerMemberWire {
157
157
  readonly state?: string;
158
158
  readonly zip?: string;
159
159
  readonly country?: string;
160
+ readonly memberSince?: string;
160
161
  readonly gender?: Gender;
161
162
  readonly status: MemberStatusWire;
162
163
  readonly sport?: Readonly<Record<string, SportRatingWire>>;
@@ -164,6 +165,30 @@ interface PartnerMemberWire {
164
165
  readonly email?: string;
165
166
  readonly grantedScopes?: readonly string[];
166
167
  }
168
+ /**
169
+ * One resolved address in a {@link MembersByEmailResultWire}.
170
+ *
171
+ * `members` is always an array: an email address is not a unique key in
172
+ * VAIR, so one address can resolve to more than one member.
173
+ *
174
+ * @category Members
175
+ */
176
+ interface PartnerMemberEmailMatchWire {
177
+ readonly email: string;
178
+ readonly members: readonly PartnerMemberWire[];
179
+ }
180
+ /**
181
+ * Raw envelope returned by `GET /partner/members/by-email`.
182
+ *
183
+ * Every requested address appears in exactly one of `matched` or
184
+ * `notFound` — the endpoint never silently drops one.
185
+ *
186
+ * @category Members
187
+ */
188
+ interface MembersByEmailResultWire {
189
+ readonly matched: readonly PartnerMemberEmailMatchWire[];
190
+ readonly notFound: readonly string[];
191
+ }
167
192
  /**
168
193
  * Raw rating change notification.
169
194
  *
@@ -356,6 +381,10 @@ interface PlayerRankOptions {
356
381
  *
357
382
  * @category Matches
358
383
  */
384
+ interface TournamentImportCreatedGhostWire {
385
+ readonly ref: string;
386
+ readonly memberId: number;
387
+ }
359
388
  interface TournamentImportResultWire {
360
389
  readonly success: boolean;
361
390
  readonly matchesImported: number;
@@ -365,6 +394,7 @@ interface TournamentImportResultWire {
365
394
  readonly dryRun?: boolean;
366
395
  readonly message?: string;
367
396
  readonly errors?: readonly string[];
397
+ readonly createdGhostMembers?: readonly TournamentImportCreatedGhostWire[];
368
398
  }
369
399
  /**
370
400
  * Wire shape for a single webhook delivery attempt.
@@ -405,6 +435,24 @@ interface ApiErrorResponse {
405
435
  readonly error?: string;
406
436
  readonly statusCode?: number;
407
437
  }
438
+ type AttributionOutcome = 'attributed' | 'already_attributed' | 'account_predates_event' | 'not_found';
439
+ interface MemberAttributionWire {
440
+ readonly memberId: number;
441
+ readonly attributed: boolean;
442
+ readonly ambassadorMemberId?: number | null;
443
+ readonly attributedAt?: string;
444
+ }
445
+ interface MembersAttributionResultWire {
446
+ readonly attributions?: readonly MemberAttributionWire[];
447
+ readonly notFound?: readonly number[];
448
+ }
449
+ interface AttributionResultWire {
450
+ readonly attributed: number;
451
+ readonly results?: readonly {
452
+ readonly memberId: number;
453
+ readonly outcome: AttributionOutcome;
454
+ }[];
455
+ }
408
456
 
409
457
  /**
410
458
  * {@link LeaderboardResource} — read-only leaderboard queries.
@@ -464,6 +512,17 @@ declare class MatchBatchResult {
464
512
  * @module
465
513
  */
466
514
 
515
+ /**
516
+ * One ghost player created by a tournament import.
517
+ *
518
+ * @category Matches
519
+ */
520
+ interface TournamentImportCreatedGhost {
521
+ /** The email or phone you supplied for this person in `ghostMembers[]`. */
522
+ readonly ref: string;
523
+ /** The public member id allocated to them, usable in `matches.submit()`. */
524
+ readonly memberId: number;
525
+ }
467
526
  /**
468
527
  * Result of a tournament import submission.
469
528
  *
@@ -478,6 +537,19 @@ declare class TournamentImportResult {
478
537
  readonly dryRun: boolean;
479
538
  readonly message: string | undefined;
480
539
  readonly errors: readonly string[];
540
+ /**
541
+ * Public member ids for the ghost players THIS import created, keyed by the
542
+ * `ref` supplied in `ghostMembers[]`.
543
+ *
544
+ * Empty on a dry-run, and empty for entries the import matched to a player who
545
+ * already existed — resolving an existing email to a member requires the
546
+ * `key:player:lookup` scope and `members.getByEmail()`.
547
+ *
548
+ * Use these ids directly in a follow-up `matches.submit()`; without them an
549
+ * import reports only how many accounts it caused and you cannot address any
550
+ * of them.
551
+ */
552
+ readonly createdGhostMembers: readonly TournamentImportCreatedGhost[];
481
553
  /** @internal */
482
554
  constructor(wire: TournamentImportResultWire);
483
555
  /** True when the import succeeded without errors. */
@@ -683,6 +755,20 @@ declare class Member {
683
755
  readonly state: string | null;
684
756
  readonly zip: string | null;
685
757
  readonly country: string | null;
758
+ /**
759
+ * The DATE this member's VAIR account was created (`YYYY-MM-DD`, UTC), or
760
+ * `null` when the endpoint does not supply it.
761
+ *
762
+ * Present on `members.get()`, `members.getBulk()` and `members.getByEmail()` —
763
+ * the calls where you already know which member you asked about. **Never on
764
+ * `members.search()`**, which is discovery: account age is not something you
765
+ * can browse strangers by.
766
+ *
767
+ * Deliberately a date, not a timestamp. It exists so you can apply a
768
+ * new-accounts-only referral rule — crediting an ambassador only for accounts
769
+ * created because of their event.
770
+ */
771
+ readonly memberSince: string | null;
686
772
  readonly gender: Gender | null;
687
773
  readonly status: MemberStatusWire;
688
774
  readonly sport: MemberSportMap;
@@ -713,6 +799,74 @@ declare class Member {
713
799
  toString(): string;
714
800
  }
715
801
 
802
+ /**
803
+ * {@link MembersByEmailResult} — result of an email → member lookup.
804
+ *
805
+ * @module
806
+ */
807
+
808
+ /**
809
+ * One requested address that resolved to at least one member.
810
+ *
811
+ * {@link members} is always an array. An email address is not a unique
812
+ * key in VAIR — an unclaimed imported record can share an address with a
813
+ * claimed account — so never assume a single element without checking.
814
+ *
815
+ * @category Members
816
+ */
817
+ declare class MemberEmailMatch {
818
+ /** The address exactly as you supplied it, not as stored. */
819
+ readonly email: string;
820
+ /** Every member holding this address. Never empty. */
821
+ readonly members: readonly Member[];
822
+ /** @internal */
823
+ constructor(wire: PartnerMemberEmailMatchWire);
824
+ /**
825
+ * The single member for this address, or `null` when the address is
826
+ * ambiguous (more than one match).
827
+ *
828
+ * Use this rather than `members[0]` when a wrong link is worse than no
829
+ * link — it refuses to guess instead of silently picking one.
830
+ */
831
+ get sole(): Member | null;
832
+ /** Whether this address resolved to more than one member. */
833
+ get isAmbiguous(): boolean;
834
+ toString(): string;
835
+ }
836
+ /**
837
+ * Result of a {@link MembersResource.getByEmail} call.
838
+ *
839
+ * Every address you supplied appears in exactly one of {@link matched} or
840
+ * {@link notFound} — the endpoint never silently drops one, so consume
841
+ * {@link notFound} directly rather than diffing your input against the
842
+ * results.
843
+ *
844
+ * A `notFound` address is not proof the person has no VAIR account:
845
+ * unclaimed imported records are deliberately excluded from this lookup.
846
+ *
847
+ * @category Members
848
+ */
849
+ declare class MembersByEmailResult {
850
+ readonly matched: readonly MemberEmailMatch[];
851
+ /** Addresses that resolved to nothing, echoed as you supplied them. */
852
+ readonly notFound: readonly string[];
853
+ /** @internal */
854
+ constructor(wire: MembersByEmailResultWire);
855
+ /**
856
+ * Look up one address's match, case-insensitively.
857
+ *
858
+ * Saves callers a linear scan and, more importantly, saves them from
859
+ * matching case-sensitively against an address the server echoed back
860
+ * in whatever case they originally sent.
861
+ */
862
+ get(email: string): MemberEmailMatch | null;
863
+ /** Whether every requested address resolved to at least one member. */
864
+ get allResolved(): boolean;
865
+ /** Total number of members across every matched address. */
866
+ get memberCount(): number;
867
+ toString(): string;
868
+ }
869
+
716
870
  /**
717
871
  * {@link RatingUpdate} — a single rating change notification.
718
872
  *
@@ -850,6 +1004,50 @@ declare class MembersResource {
850
1004
  getBulk(ids: number[], options?: {
851
1005
  sport?: string;
852
1006
  }): Promise<Member[]>;
1007
+ /**
1008
+ * Resolve up to 100 members by their **exact** email address.
1009
+ *
1010
+ * Use this to link your users to their VAIR identity when you hold
1011
+ * their email but not their member ID — e.g. resolving a tournament
1012
+ * roster at registration instead of waiting for each player to
1013
+ * complete SSO.
1014
+ *
1015
+ * **Requires the `key:player:lookup` scope**, which is granted per
1016
+ * partner on approval. Holding `key:player:search` does not imply it.
1017
+ *
1018
+ * Matching is exact and case-insensitive; there is deliberately no
1019
+ * partial, prefix or fuzzy matching. Every address you supply comes
1020
+ * back in either `matched` or `notFound`, so read `notFound` directly
1021
+ * instead of diffing your input against the results.
1022
+ *
1023
+ * A `notFound` address is **not** proof the person has no VAIR
1024
+ * account — unclaimed imported records are excluded from this lookup.
1025
+ *
1026
+ * @param emails - Email addresses to resolve (max 100).
1027
+ * @param options - Optional filters.
1028
+ * @param options.sport - Sport code to scope ratings (e.g. `'pickleball'`).
1029
+ * @throws {@link ValidationError} If more than 100 addresses are
1030
+ * provided, or the list is empty.
1031
+ * @category Members
1032
+ *
1033
+ * @example
1034
+ * ```ts
1035
+ * const result = await client.members.getByEmail([
1036
+ * 'ada@example.com',
1037
+ * 'nobody@example.com',
1038
+ * ]);
1039
+ *
1040
+ * for (const match of result.matched) {
1041
+ * const member = match.sole; // null when the address is ambiguous
1042
+ * if (member) console.log(match.email, '->', member.memberId);
1043
+ * }
1044
+ *
1045
+ * console.log('no VAIR account found for:', result.notFound);
1046
+ * ```
1047
+ */
1048
+ getByEmail(emails: readonly string[], options?: {
1049
+ sport?: string;
1050
+ }): Promise<MembersByEmailResult>;
853
1051
  /**
854
1052
  * Poll for rating change notifications.
855
1053
  *
@@ -1050,6 +1248,170 @@ declare class OAuthResource {
1050
1248
  }[]>;
1051
1249
  }
1052
1250
 
1251
+ /**
1252
+ * {@link MemberAttribution}, {@link MembersAttributionResult} and
1253
+ * {@link AttributionResult} — ambassador referral credit.
1254
+ *
1255
+ * @module
1256
+ */
1257
+
1258
+ /**
1259
+ * Who currently earns referral credit for one member.
1260
+ *
1261
+ * @category Referrals
1262
+ */
1263
+ declare class MemberAttribution {
1264
+ readonly memberId: number;
1265
+ /** True when some ambassador already holds credit for this member. */
1266
+ readonly attributed: boolean;
1267
+ /**
1268
+ * The member id of the ambassador holding the credit.
1269
+ *
1270
+ * Compare it against your own event host's member id to tell "already credited
1271
+ * to my host — nothing to do" from "credited to somebody else — a person needs
1272
+ * to look, because claiming it takes credit from them".
1273
+ *
1274
+ * `null` both when nobody holds credit and when credit is held by a record that
1275
+ * has no member id of its own, so check {@link attributed} to tell those apart.
1276
+ */
1277
+ readonly ambassadorMemberId: number | null;
1278
+ /** The date the credit was established (`YYYY-MM-DD`), or `null`. */
1279
+ readonly attributedAt: string | null;
1280
+ /** @internal */
1281
+ constructor(wire: MemberAttributionWire);
1282
+ /** True when nobody holds credit yet, so this member can be claimed. */
1283
+ get isClaimable(): boolean;
1284
+ /** True when credit is held by an ambassador OTHER than the one given. */
1285
+ heldBySomeoneOtherThan(ambassadorMemberId: number): boolean;
1286
+ }
1287
+ /**
1288
+ * Attribution for a batch of members, plus the ids that matched nothing.
1289
+ *
1290
+ * @category Referrals
1291
+ */
1292
+ declare class MembersAttributionResult {
1293
+ readonly attributions: readonly MemberAttribution[];
1294
+ /**
1295
+ * Member ids that matched no member. Read this rather than diffing your input
1296
+ * against the results — every id you sent lands in one bucket or the other.
1297
+ */
1298
+ readonly notFound: readonly number[];
1299
+ /** @internal */
1300
+ constructor(wire: MembersAttributionResultWire);
1301
+ /** Attribution for one member id, or `undefined` if it was not returned. */
1302
+ get(memberId: number): MemberAttribution | undefined;
1303
+ /** Members nobody holds credit for yet. */
1304
+ get claimable(): readonly MemberAttribution[];
1305
+ }
1306
+ /**
1307
+ * Outcome of submitting attribution for a batch of members.
1308
+ *
1309
+ * @category Referrals
1310
+ */
1311
+ declare class AttributionResult {
1312
+ /** How many members were newly attributed by this request. */
1313
+ readonly attributed: number;
1314
+ /** One entry per member id supplied, in the order supplied. */
1315
+ readonly results: readonly Readonly<{
1316
+ memberId: number;
1317
+ outcome: AttributionOutcome;
1318
+ }>[];
1319
+ /** @internal */
1320
+ constructor(wire: AttributionResultWire);
1321
+ /** Member ids with the given outcome. */
1322
+ withOutcome(outcome: AttributionOutcome): readonly number[];
1323
+ /**
1324
+ * Members already credited to somebody. These are the ones worth a human
1325
+ * look — it may be your own host, or it may be another ambassador.
1326
+ */
1327
+ get alreadyAttributed(): readonly number[];
1328
+ /**
1329
+ * Members rejected because their account pre-dates the event's registration
1330
+ * page. The event did not recruit them, so no credit is due.
1331
+ */
1332
+ get predatedEvent(): readonly number[];
1333
+ }
1334
+
1335
+ /**
1336
+ * {@link ReferralsResource} — read and record ambassador referral credit.
1337
+ *
1338
+ * @module
1339
+ */
1340
+
1341
+ /**
1342
+ * Read and record which ambassador earns referral credit for a player.
1343
+ *
1344
+ * Each method needs its own API-key permission, granted per partner:
1345
+ * `key:referral:read` for {@link get} and `key:referral:write` for
1346
+ * {@link attribute}. Neither is implied by a general read, write or admin key —
1347
+ * reading attribution exposes who recruited whom, and writing it decides who
1348
+ * earns commission.
1349
+ *
1350
+ * @category Resources
1351
+ */
1352
+ declare class ReferralsResource {
1353
+ #private;
1354
+ /** @internal */
1355
+ constructor(http: HttpTransport);
1356
+ /**
1357
+ * Who currently earns referral credit for these members.
1358
+ *
1359
+ * Use this before {@link attribute} to tell the two cases apart that matter:
1360
+ * a member already credited to your own event host (nothing to do) and one
1361
+ * credited to a different ambassador (a person should look, because claiming
1362
+ * it takes credit from them).
1363
+ *
1364
+ * @example
1365
+ * ```ts
1366
+ * const result = await client.referrals.get([4873327, 4873328]);
1367
+ *
1368
+ * for (const a of result.claimable) {
1369
+ * console.log(a.memberId, 'has no credit yet');
1370
+ * }
1371
+ * console.log('no such member:', result.notFound);
1372
+ * ```
1373
+ *
1374
+ * @throws {@link ValidationError} If the list is empty or exceeds 100 ids.
1375
+ */
1376
+ get(memberIds: readonly number[]): Promise<MembersAttributionResult>;
1377
+ /**
1378
+ * Credit an ambassador for players their event recruited.
1379
+ *
1380
+ * `registrationPublishedAt` is the date the event's registration page was
1381
+ * **first published**. Accounts created before it did not come from the event
1382
+ * and are rejected with `account_predates_event`. VAIR applies that rule
1383
+ * itself, so every partner is held to the same one.
1384
+ *
1385
+ * VAIR cannot verify the date — it holds no record of your registration pages —
1386
+ * so the value you send is recorded for audit. Send the real one.
1387
+ *
1388
+ * Safe to retry: attribution is one-per-player forever, enforced by the
1389
+ * database, so a resubmitted player returns `already_attributed` and nothing
1390
+ * changes.
1391
+ *
1392
+ * @example
1393
+ * ```ts
1394
+ * const result = await client.referrals.attribute({
1395
+ * referralCode: 'hillhurst-open',
1396
+ * registrationPublishedAt: '2026-08-01',
1397
+ * memberIds: [4873327, 4873328],
1398
+ * });
1399
+ *
1400
+ * console.log(result.attributed, 'newly credited');
1401
+ * console.log('need a human:', result.alreadyAttributed);
1402
+ * console.log('too old to credit:', result.predatedEvent);
1403
+ * ```
1404
+ *
1405
+ * @throws {@link ValidationError} If the list is empty or exceeds 500 ids, or
1406
+ * the date is not `YYYY-MM-DD`.
1407
+ */
1408
+ attribute(input: {
1409
+ referralCode: string;
1410
+ registrationPublishedAt: string;
1411
+ memberIds: readonly number[];
1412
+ }): Promise<AttributionResult>;
1413
+ }
1414
+
1053
1415
  /**
1054
1416
  * {@link WebhookDelivery} and {@link WebhookDeliveriesResult} — webhook
1055
1417
  * delivery inspection models.
@@ -1202,6 +1564,11 @@ declare class Vairified {
1202
1564
  readonly leaderboard: LeaderboardResource;
1203
1565
  /** Webhook delivery inspection — deliveries. */
1204
1566
  readonly webhooks: WebhooksResource;
1567
+ /**
1568
+ * Read and record ambassador referral credit. Each method needs its own
1569
+ * per-partner permission — see {@link ReferralsResource}.
1570
+ */
1571
+ readonly referrals: ReferralsResource;
1205
1572
  constructor(options?: VairifiedOptions);
1206
1573
  /**
1207
1574
  * API usage statistics for the current API key.
@@ -1300,4 +1667,4 @@ declare class OAuthError extends VairifiedError {
1300
1667
  constructor(message?: string, errorCode?: string, response?: unknown);
1301
1668
  }
1302
1669
 
1303
- 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 };
1670
+ 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, MemberEmailMatch, MemberSportMap, type MemberStatusWire, MembersByEmailResult, type MembersByEmailResultWire, MembersResource, NotFoundError, type OAuthConfig, OAuthError, OAuthResource, type OAuthScope, type PartnerMemberEmailMatchWire, 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 };