@rekey.dev/node 1.1.2

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.
@@ -0,0 +1,848 @@
1
+ /**
2
+ * @rekey.dev/node — server SDK for Rekey.
3
+ *
4
+ * One client instance per Application. Construct with the Application's
5
+ * secret key (`rp_live_…` or `rp_test_…`) and the URL of your Rekey
6
+ * deployment. Never ship the secret key to the browser — for browser code
7
+ * use `@rekey.dev/react` with the Application's public key instead.
8
+ *
9
+ * @example Smoke-test your credentials
10
+ * ```ts
11
+ * import { Rekey } from "@rekey.dev/node";
12
+ *
13
+ * const rekey = new Rekey({
14
+ * apiUrl: process.env.RELIPAY_URL!,
15
+ * secretKey: process.env.RELIPAY_SECRET!,
16
+ * });
17
+ *
18
+ * const me = await rekey.applications.me();
19
+ * console.log(`Connected to "${me.name}" (${me.slug})`);
20
+ * ```
21
+ */
22
+ import type { ApplicationDto, AuthResultDto, JwksDto, ChangePasswordRequest, CheckoutResultDto, ConsumeCreditsRequest, ConsumeCreditsResultDto, CreateCheckoutRequest, CreditBalanceDto, CreditLedgerEntryDto, EndUserDto, ForgotPasswordRequest, ForgotPasswordResultDto, LicenseVerifyResultDto, MfaVerifyRequest, OAuthAuthServerMetadata, OAuthIntrospectionResponse, OrganizationDto, OrganizationInvitationDto, OrganizationMemberDto, OrganizationWithRoleDto, PlanDto, ProvidersListDto, ResetPasswordRequest, SignInOutcomeDto, SignInRequest, SignUpRequest, SubscriptionDto, UsageAggregateDto, UsageRecordDto, ValidateCouponRequest, ValidateCouponResultDto } from '@rekey.dev/shared-types';
23
+ import { RekeyError } from '@rekey.dev/shared-types';
24
+ export type { ApplicationDto, EndUserDto, ApiKeyDto, AuthResultDto, MfaChallengeResultDto, MfaVerifyRequest, SignInOutcomeDto, SignInRequest, SignUpRequest, RefreshRequest, ForgotPasswordRequest, ForgotPasswordResultDto, ResetPasswordRequest, ChangePasswordRequest, PlanDto, SubscriptionDto, CreateCheckoutRequest, CheckoutResultDto, CouponDto, ValidateCouponRequest, ValidateCouponResultDto, CouponDiscountTypeValue, PlanIntervalType, PlanKindType, LicenseKindType, CreditReasonType, CreditBalanceDto, CreditLedgerEntryDto, ConsumeCreditsRequest, ConsumeCreditsResultDto, OrganizationDto, OrganizationWithRoleDto, OrganizationMemberDto, OrganizationInvitationDto, OrganizationRole, LicenseDto, LicenseStatusType, LicenseVerifyResultDto, UsageRecordDto, UsageAggregateDto, SubscriptionStatusType, RekeyErrorShape, AuthConfig, BillingConfig, BillingProvider, TokenAlg, JwkRsaPublic, JwksDto, OAuthIntrospectionResponse, OAuthAuthServerMetadata, } from '@rekey.dev/shared-types';
25
+ /** Configuration for a Rekey client instance. */
26
+ export interface ReliPayConfig {
27
+ /** Base URL of the Rekey API. e.g. `https://rekey.example.com` */
28
+ apiUrl: string;
29
+ /** Secret key for one Application — `rp_live_…` or `rp_test_…`. Never ship to the browser. */
30
+ secretKey: string;
31
+ /** Optional fetch override (test stubs, custom keep-alive agents, etc.). */
32
+ fetch?: typeof fetch;
33
+ }
34
+ export { RekeyError };
35
+ /**
36
+ * Outbound webhook event registry — the events Rekey can POST to your app
37
+ * (verify them with `verifyWebhookSignature` below). `WEBHOOK_EVENTS` carries
38
+ * `{ name, description }` pairs for introspection/autocomplete;
39
+ * `KNOWN_WEBHOOK_EVENTS` is just the names. Mirrors the API's registry exactly.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { WEBHOOK_EVENTS, isKnownWebhookEvent, type WebhookEventEnvelope } from '@rekey.dev/node';
44
+ *
45
+ * for (const e of WEBHOOK_EVENTS) console.log(`${e.name} — ${e.description}`);
46
+ *
47
+ * const event = req.body as WebhookEventEnvelope; // after verifyWebhookSignature(...)
48
+ * if (event.type === 'subscription.activated') unlockPlan(event.data);
49
+ * ```
50
+ */
51
+ export { WEBHOOK_EVENTS, KNOWN_WEBHOOK_EVENTS, isKnownWebhookEvent } from '@rekey.dev/shared-types';
52
+ export type { WebhookEventType, WebhookEventEnvelope } from '@rekey.dev/shared-types';
53
+ /**
54
+ * Top-level Rekey client. Auth and billing live as namespaces
55
+ * (`rekey.applications`, `rekey.auth`, `rekey.billing`) so an agent
56
+ * reading `rekey.` in an editor sees a discoverable surface.
57
+ */
58
+ export declare class Rekey {
59
+ private readonly apiUrl;
60
+ private readonly secretKey;
61
+ private readonly fetchImpl;
62
+ /** Operations on the calling Application itself. */
63
+ readonly applications: ApplicationsClient;
64
+ /** Auth operations — sign-in, sign-up, sessions, passkeys, magic-link. */
65
+ readonly auth: AuthClient;
66
+ /** Billing operations — plans, checkout, subscriptions, coupons. */
67
+ readonly billing: BillingClient;
68
+ /** End-user organizations — create, invite, members, role changes. */
69
+ readonly organizations: OrganizationsClient;
70
+ /** License key verification + activation. */
71
+ readonly licenses: LicensesClient;
72
+ /** Usage metering — record events, aggregate windows. */
73
+ readonly usage: UsageClient;
74
+ /** Prepaid credits — balance reads, idempotent drawdown, ledger. */
75
+ readonly credits: CreditsClient;
76
+ /** MCP — validate Rekey-issued MCP tokens from your own MCP server. */
77
+ readonly mcp: McpClient;
78
+ constructor(config: ReliPayConfig);
79
+ /** @internal */
80
+ request<T>(method: string, path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
81
+ /**
82
+ * @internal Raw request for the non-enveloped OAuth/MCP endpoints — returns
83
+ * the parsed JSON as-is (those endpoints emit standard OAuth shapes, not the
84
+ * `{ success, data }` envelope). Throws `RekeyError` on non-2xx, mapping
85
+ * the OAuth `{ error, error_description }` body when present.
86
+ */
87
+ requestRaw<T>(method: string, path: string, body?: unknown, auth?: boolean): Promise<T>;
88
+ }
89
+ /**
90
+ * MCP helpers for customers running their OWN MCP server behind Rekey auth.
91
+ * The hosted MCP server (account tools) is consumed by MCP clients directly —
92
+ * this client is for the "bring your own MCP server" path: validate incoming
93
+ * Rekey-issued tokens, and read the OAuth metadata.
94
+ */
95
+ declare class McpClient {
96
+ private readonly client;
97
+ private slugCache;
98
+ constructor(client: Rekey);
99
+ private slug;
100
+ /**
101
+ * Validate an MCP access token (RFC 7662 introspection). Call this from your
102
+ * own MCP server to authorize an incoming request. Authenticated with this
103
+ * client's secret key.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * const result = await rekey.mcp.introspect(bearerToken);
108
+ * if (!result.active) throw new Error('unauthorized');
109
+ * const endUserId = result.sub;
110
+ * ```
111
+ */
112
+ introspect(token: string): Promise<OAuthIntrospectionResponse>;
113
+ /** Fetch this application's OAuth authorization-server metadata (RFC 8414). */
114
+ metadata(): Promise<OAuthAuthServerMetadata>;
115
+ }
116
+ declare class ApplicationsClient {
117
+ private readonly client;
118
+ constructor(client: Rekey);
119
+ /**
120
+ * Verify credentials and fetch the calling Application. Use this as your
121
+ * SDK smoke test — if it returns, your secret key is good and you're
122
+ * pointed at the right Rekey deployment.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * const me = await rekey.applications.me();
127
+ * console.log(`Connected to "${me.name}" (${me.slug})`);
128
+ * ```
129
+ *
130
+ * @throws {RekeyError} with `code: "API_KEY_INVALID"` if the key is wrong/revoked/expired.
131
+ */
132
+ me(): Promise<ApplicationDto>;
133
+ }
134
+ declare class AuthClient {
135
+ private readonly client;
136
+ constructor(client: Rekey);
137
+ /**
138
+ * Create a new end-user in the calling Application via email + password.
139
+ * Returns the user and a JWT to use for subsequent per-user calls
140
+ * (e.g. `getCurrentUser(token)`).
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const { endUser, token } = await rekey.auth.signUp({
145
+ * email: 'alice@example.com',
146
+ * password: 'correct-horse-battery-staple',
147
+ * });
148
+ * // store token in your session, return it to the browser, etc.
149
+ * ```
150
+ *
151
+ * @throws {RekeyError} `EMAIL_ALREADY_EXISTS` (409) if the email is taken in this Application.
152
+ * @throws {RekeyError} `PASSWORD_TOO_SHORT` (400) if shorter than the Application's `passwordMinLength`.
153
+ * @throws {RekeyError} `AUTH_METHOD_DISABLED` (400) if the Application doesn't have `"password"` enabled.
154
+ */
155
+ signUp(input: SignUpRequest): Promise<AuthResultDto>;
156
+ /**
157
+ * Authenticate an existing end-user with email + password.
158
+ *
159
+ * Returns a discriminated union over `mfaRequired`:
160
+ * - `mfaRequired === false` → full `AuthResultDto` with access+refresh.
161
+ * - `mfaRequired === true` → `mfaChallengeToken` (5-minute lifetime).
162
+ * Prompt the user for their TOTP / backup code and call
163
+ * `mfaVerify({ mfaChallengeToken, code })` to receive a real session.
164
+ *
165
+ * **Branch on `result.mfaRequired` before reading `accessToken`** — the
166
+ * MFA-required branch has no session tokens.
167
+ *
168
+ * @throws {RekeyError} `INVALID_CREDENTIALS` (401) — single code on purpose.
169
+ * Don't try to distinguish wrong-email from wrong-password from the SDK side either.
170
+ */
171
+ signIn(input: SignInRequest): Promise<SignInOutcomeDto>;
172
+ /**
173
+ * Exchange an MFA challenge token + TOTP/backup code for a real session.
174
+ * Use after `signIn` (or OAuth callback) returns `mfaRequired: true`.
175
+ *
176
+ * @throws {RekeyError} `MFA_CHALLENGE_INVALID` (401) if the token is
177
+ * forged, expired, or signed with a different secret.
178
+ * @throws {RekeyError} `MFA_CHALLENGE_WRONG_APPLICATION` (401) if the
179
+ * token was issued under a different Application.
180
+ * @throws {RekeyError} `MFA_CODE_INVALID` (401) if the code doesn't
181
+ * verify against the user's TOTP secret or remaining backup codes.
182
+ */
183
+ mfaVerify(input: MfaVerifyRequest): Promise<AuthResultDto>;
184
+ /**
185
+ * Request a magic-link sign-in email. Enumeration-safe: same response
186
+ * shape whether the email exists or not. When the Application has
187
+ * email transport configured, the link is sent and `magicLinkToken`
188
+ * is null; otherwise the raw token is returned for you to forward.
189
+ */
190
+ requestMagicLink(input: {
191
+ email: string;
192
+ signInUrl?: string;
193
+ }): Promise<{
194
+ delivered: boolean;
195
+ emailSent: boolean;
196
+ magicLinkToken: string | null;
197
+ }>;
198
+ /**
199
+ * Consume a magic-link token. Returns `SignInOutcome` — branch on
200
+ * `mfaRequired` before reading `accessToken`. For MFA-enrolled users
201
+ * the response carries `mfaChallengeToken` and you must complete via
202
+ * `mfaVerify(...)`.
203
+ */
204
+ verifyMagicLink(input: {
205
+ token: string;
206
+ }): Promise<SignInOutcomeDto>;
207
+ /**
208
+ * Begin a passkey authentication ceremony. Returns the WebAuthn options
209
+ * to forward to the browser (`navigator.credentials.get(...)`) along
210
+ * with `expectedChallenge` — bind the challenge to your session and
211
+ * pass both back via `verifyPasskeyAuthentication(...)`.
212
+ */
213
+ startPasskeyAuthentication(input?: {
214
+ email?: string;
215
+ }): Promise<{
216
+ options: unknown;
217
+ expectedChallenge: string;
218
+ }>;
219
+ /**
220
+ * Complete a passkey authentication. Returns the same `SignInOutcome`
221
+ * shape as `signIn` — but passkeys are themselves a strong factor, so
222
+ * `mfaRequired` will always be `false` in practice.
223
+ */
224
+ verifyPasskeyAuthentication(input: {
225
+ response: unknown;
226
+ expectedChallenge: string;
227
+ }): Promise<SignInOutcomeDto>;
228
+ /**
229
+ * Begin a passkey registration ceremony for an authenticated user.
230
+ * Forward `options` to `navigator.credentials.create(...)`; store
231
+ * `expectedChallenge` in session; POST both back via
232
+ * `verifyPasskeyRegistration(...)`.
233
+ */
234
+ startPasskeyRegistration(accessToken: string): Promise<{
235
+ options: unknown;
236
+ expectedChallenge: string;
237
+ }>;
238
+ verifyPasskeyRegistration(accessToken: string, input: {
239
+ response: unknown;
240
+ expectedChallenge: string;
241
+ deviceName?: string;
242
+ }): Promise<{
243
+ credentialId: string;
244
+ deviceName: string | null;
245
+ }>;
246
+ /** List the user's registered passkeys. */
247
+ listPasskeys(accessToken: string): Promise<Array<{
248
+ id: string;
249
+ credentialId: string;
250
+ deviceName: string | null;
251
+ lastUsedAt: string | null;
252
+ createdAt: string;
253
+ }>>;
254
+ /** Remove a passkey. Returns `{deleted: false}` if the row doesn't belong to this user. */
255
+ deletePasskey(accessToken: string, credentialRowId: string): Promise<{
256
+ deleted: boolean;
257
+ }>;
258
+ /**
259
+ * Resolve the end-user behind a presented access token.
260
+ *
261
+ * @throws {RekeyError} `USER_TOKEN_INVALID` (401) if expired/forged/wrong-secret.
262
+ * @throws {RekeyError} `USER_TOKEN_WRONG_APPLICATION` (401) if the token was issued
263
+ * by a different Application than the calling secret key represents.
264
+ */
265
+ getCurrentUser(accessToken: string): Promise<EndUserDto & {
266
+ activeOrganizationId: string | null;
267
+ }>;
268
+ /**
269
+ * Exchange a refresh token for a fresh {access, refresh} pair. The presented
270
+ * refresh is revoked atomically — call this **once** and store the new
271
+ * `refreshToken` from the response immediately.
272
+ *
273
+ * @throws {RekeyError} `REFRESH_TOKEN_REUSED` (401) if you replay an already-used token.
274
+ * This is a strong signal the original was leaked; treat as compromise.
275
+ * @throws {RekeyError} `REFRESH_TOKEN_EXPIRED` (401) after the 30-day refresh window.
276
+ */
277
+ refresh(refreshToken: string): Promise<AuthResultDto>;
278
+ /**
279
+ * Revoke a refresh token. Idempotent — no-op for unknown tokens. The
280
+ * access token paired with this refresh remains valid until its short
281
+ * (15 min) expiry; for true "log out everywhere" semantics, also clear
282
+ * the access token from your client.
283
+ */
284
+ signOut(refreshToken: string): Promise<{
285
+ signedOut: true;
286
+ }>;
287
+ /**
288
+ * Request a password-reset token for an email. Always succeeds — never
289
+ * tells you whether the email exists. **You must email the returned
290
+ * `resetToken` to the user**: Rekey does not send email.
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * const { resetToken } = await rekey.auth.requestPasswordReset({ email });
295
+ * if (resetToken) await sendgrid.send({ to: email, subject: 'Reset', text: `link: ${url(resetToken)}` });
296
+ * ```
297
+ */
298
+ requestPasswordReset(input: ForgotPasswordRequest): Promise<ForgotPasswordResultDto>;
299
+ /**
300
+ * Consume a reset token + set a new password. Single-use. On success,
301
+ * every refresh token for the user is revoked.
302
+ *
303
+ * @throws {RekeyError} `PASSWORD_RESET_TOKEN_INVALID` / `_USED` / `_EXPIRED` / `_WRONG_APPLICATION`
304
+ * @throws {RekeyError} `PASSWORD_TOO_SHORT` if below the Application's `passwordMinLength`
305
+ */
306
+ resetPassword(input: ResetPasswordRequest): Promise<{
307
+ ok: true;
308
+ }>;
309
+ /**
310
+ * Authenticated password change. Pass the user's *current* access token.
311
+ * On success, every refresh token for the user is revoked — other devices
312
+ * are signed out.
313
+ */
314
+ changePassword(accessToken: string, input: ChangePasswordRequest): Promise<{
315
+ ok: true;
316
+ }>;
317
+ /**
318
+ * Revoke every refresh token for the calling user. "Sign out of all
319
+ * devices." The caller's access token remains valid until 15-min expiry
320
+ * — clear it client-side for full logout.
321
+ */
322
+ signOutEverywhere(accessToken: string): Promise<{
323
+ revokedCount: number;
324
+ }>;
325
+ /**
326
+ * Send (or re-send) an email-verification link to the current user.
327
+ * If email transport is configured on the Application, Rekey sends
328
+ * the email and `verificationToken` is null. Otherwise the raw token
329
+ * is returned for the caller to forward via their own provider.
330
+ *
331
+ * Pass `verifyUrl` containing `{token}` to template the link target
332
+ * (e.g. `https://app.example.com/verify?t={token}`).
333
+ */
334
+ sendVerificationEmail(accessToken: string, input?: {
335
+ verifyUrl?: string;
336
+ }): Promise<{
337
+ emailSent: boolean;
338
+ verificationToken: string | null;
339
+ }>;
340
+ /**
341
+ * Consume an email-verification token. Single-use, 24-hour lifetime.
342
+ * Marks `emailVerified: true` on the user record. Cross-Application
343
+ * tokens are refused with `EMAIL_VERIFICATION_TOKEN_WRONG_APPLICATION`.
344
+ */
345
+ verifyEmail(input: {
346
+ token: string;
347
+ }): Promise<{
348
+ verified: true;
349
+ endUser: EndUserDto;
350
+ }>;
351
+ /**
352
+ * List the current user's active sessions (live refresh tokens), newest
353
+ * first. Each carries the User-Agent + IP captured at issue time and an
354
+ * `id` you can pass to `revokeSession(...)`.
355
+ */
356
+ listSessions(accessToken: string): Promise<Array<{
357
+ id: string;
358
+ createdAt: string;
359
+ expiresAt: string;
360
+ userAgent: string | null;
361
+ ip: string | null;
362
+ }>>;
363
+ /** Revoke one session by id. Idempotent — `{ revoked: false }` if it isn't this user's. */
364
+ revokeSession(accessToken: string, sessionId: string): Promise<{
365
+ revoked: boolean;
366
+ }>;
367
+ /** MFA enrollment status for the current user, plus the Application's policy. */
368
+ mfaStatus(accessToken: string): Promise<{
369
+ enabled: boolean;
370
+ remainingBackupCodes: number | null;
371
+ policy: 'off' | 'optional' | 'required';
372
+ }>;
373
+ /**
374
+ * Begin TOTP enrollment: mints a secret (as an `otpauthUrl` for the QR) and
375
+ * 10 single-show backup codes. **Not enrolled until `confirmMfaSetup(...)`.**
376
+ * Only SHA-256 hashes of the backup codes are stored — show them once.
377
+ */
378
+ mfaSetup(accessToken: string): Promise<{
379
+ otpauthUrl: string;
380
+ backupCodes: string[];
381
+ warning: string;
382
+ }>;
383
+ /** Confirm enrollment by submitting the current 6-digit TOTP code. */
384
+ confirmMfaSetup(accessToken: string, code: string): Promise<{
385
+ ok: true;
386
+ }>;
387
+ /**
388
+ * Verify a TOTP or backup code as a step-up check (does NOT issue a session).
389
+ * Backup codes are single-use — consumed on success. Returns `{ ok }`.
390
+ */
391
+ mfaChallenge(accessToken: string, code: string): Promise<{
392
+ ok: boolean;
393
+ }>;
394
+ /** Disable MFA for the current user. */
395
+ disableMfa(accessToken: string): Promise<{
396
+ disabled: true;
397
+ }>;
398
+ /**
399
+ * Get the provider authorization URL to redirect the browser to. Pass an
400
+ * unguessable `state` and verify it on return before calling `completeOAuth`.
401
+ */
402
+ startOAuth(provider: string, state: string): Promise<{
403
+ authorizationUrl: string;
404
+ }>;
405
+ /**
406
+ * Exchange the provider `code` for a Rekey session. Returns a
407
+ * `SignInOutcome` — branch on `mfaRequired` before reading `accessToken`.
408
+ * Verify the `state` CSRF value yourself before calling.
409
+ */
410
+ completeOAuth(provider: string, code: string): Promise<SignInOutcomeDto>;
411
+ /** List the OAuth providers linked to the current user. */
412
+ listOAuthIdentities(accessToken: string): Promise<Array<{
413
+ provider: string;
414
+ providerAccountId: string;
415
+ email: string | null;
416
+ createdAt: string;
417
+ }>>;
418
+ /** Begin linking a provider to the *currently authenticated* user. */
419
+ startOAuthLink(accessToken: string, provider: string, state: string): Promise<{
420
+ authorizationUrl: string;
421
+ }>;
422
+ /**
423
+ * Complete an OAuth link — attaches the provider identity to the current
424
+ * user. Refuses on unverified provider emails (account-takeover guard) or
425
+ * when the provider account already belongs to a different user.
426
+ */
427
+ completeOAuthLink(accessToken: string, provider: string, code: string): Promise<{
428
+ provider: string;
429
+ providerAccountId: string;
430
+ alreadyLinked: boolean;
431
+ }>;
432
+ /**
433
+ * Remove a linked provider. Refuses with `OAUTH_UNLINK_WOULD_LOCK_OUT` (409)
434
+ * if it would leave the account with no way to sign in.
435
+ */
436
+ unlinkOAuth(accessToken: string, provider: string): Promise<{
437
+ unlinked: boolean;
438
+ }>;
439
+ }
440
+ /** Optional offset pagination for list endpoints. The API caps these lists
441
+ * (default 50, max 100); pass `offset` to page beyond the first window. */
442
+ export interface ListPage {
443
+ limit?: number;
444
+ offset?: number;
445
+ }
446
+ declare class OrganizationsClient {
447
+ private readonly client;
448
+ constructor(client: Rekey);
449
+ /** Create an organization; the calling user becomes the OWNER. */
450
+ create(accessToken: string, input: {
451
+ name: string;
452
+ slug: string;
453
+ metadata?: Record<string, unknown>;
454
+ }): Promise<{
455
+ organization: OrganizationDto;
456
+ membership: {
457
+ id: string;
458
+ role: 'OWNER';
459
+ };
460
+ }>;
461
+ /** List organizations the calling user belongs to, with their role. The
462
+ * result is paginated (default 50, max 100); pass `page.offset` for more. */
463
+ listMine(accessToken: string, page?: ListPage): Promise<OrganizationWithRoleDto[]>;
464
+ /** Fetch one organization the caller belongs to. */
465
+ get(accessToken: string, organizationId: string): Promise<OrganizationWithRoleDto>;
466
+ /** Update org name / metadata. OWNER + ADMIN only. */
467
+ update(accessToken: string, organizationId: string, input: {
468
+ name?: string;
469
+ metadata?: Record<string, unknown>;
470
+ }): Promise<OrganizationDto>;
471
+ /** List members of an organization the caller belongs to. Paginated
472
+ * (default 50, max 100); pass `page.offset` to page beyond the first window. */
473
+ listMembers(accessToken: string, organizationId: string, page?: ListPage): Promise<OrganizationMemberDto[]>;
474
+ /**
475
+ * Invite a user. Returns the raw token ONCE — surface via your own
476
+ * email/share channel. OWNER + ADMIN only.
477
+ */
478
+ invite(accessToken: string, organizationId: string, input: {
479
+ email: string;
480
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
481
+ }): Promise<{
482
+ invitation: OrganizationInvitationDto;
483
+ token: string;
484
+ }>;
485
+ /** Revoke a pending invitation. OWNER + ADMIN only. Idempotent. */
486
+ revokeInvitation(accessToken: string, organizationId: string, invitationId: string): Promise<{
487
+ revoked: boolean;
488
+ }>;
489
+ /**
490
+ * Change a member's role. OWNER manages anyone; ADMIN manages MEMBER
491
+ * only. Last-OWNER guard refuses demoting the only OWNER.
492
+ */
493
+ setMemberRole(accessToken: string, organizationId: string, targetEndUserId: string, input: {
494
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
495
+ }): Promise<{
496
+ id: string;
497
+ organizationId: string;
498
+ endUserId: string;
499
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
500
+ }>;
501
+ /**
502
+ * Remove a member (or self). Refuses removing the last OWNER.
503
+ *
504
+ * Idempotent: `removed` is `false` when the target was not a member (e.g.
505
+ * already removed) — a no-op removal is not an error. Branch on `removed`
506
+ * rather than assuming it is always `true`.
507
+ */
508
+ removeMember(accessToken: string, organizationId: string, targetEndUserId: string): Promise<{
509
+ removed: boolean;
510
+ }>;
511
+ /**
512
+ * Self-leave. An OWNER cannot leave (payment + benefits are tied to the
513
+ * owner — `ORGANIZATION_OWNER_CANNOT_LEAVE`); transfer ownership via support
514
+ * first, or demote yourself to ADMIN if there is another OWNER.
515
+ */
516
+ leave(accessToken: string, organizationId: string): Promise<{
517
+ removed: boolean;
518
+ }>;
519
+ /**
520
+ * Accept an organization invitation by raw token. Refuses cross-
521
+ * Application invitations. Idempotent if the caller is already a member.
522
+ */
523
+ acceptInvitation(accessToken: string, input: {
524
+ token: string;
525
+ }): Promise<{
526
+ membership: {
527
+ id: string;
528
+ organizationId: string;
529
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
530
+ };
531
+ }>;
532
+ /**
533
+ * Make `organizationId` the active org for this session (member-only).
534
+ * Returns a fresh {accessToken, refreshToken} pair carrying the active org —
535
+ * **store both**. Subsequent entitlement reads (`billing.getEntitlements`)
536
+ * then default to this org's view + shared pool without passing
537
+ * `organizationId` explicitly. The active org survives token refresh until
538
+ * you switch again, clear it, or leave the org.
539
+ */
540
+ switch(accessToken: string, organizationId: string): Promise<AuthResultDto>;
541
+ /**
542
+ * Clear the active org — switch the session back to the personal pool.
543
+ * Returns a fresh token pair (no active org); **store both**.
544
+ */
545
+ clearActive(accessToken: string): Promise<AuthResultDto>;
546
+ }
547
+ declare class LicensesClient {
548
+ private readonly client;
549
+ constructor(client: Rekey);
550
+ /**
551
+ * Verify a license key + record an activation for this machine. Call
552
+ * once at app startup; you'll get a deterministic body (`ok=false` for
553
+ * invalid licenses — never an HTTP error — so your software can loop
554
+ * on the result without try/catch noise).
555
+ *
556
+ * `machineFingerprint` should be a stable identifier you derive client-
557
+ * side (hostname + OS + mac address, hashed). The same fingerprint
558
+ * across re-verifications does NOT consume a new seat.
559
+ *
560
+ * @example
561
+ * ```ts
562
+ * const result = await rekey.licenses.verify({
563
+ * key,
564
+ * machineFingerprint,
565
+ * label: 'Adam\'s MacBook',
566
+ * });
567
+ * if (!result.ok) showLicenseError(result.reason);
568
+ * ```
569
+ */
570
+ verify(input: {
571
+ key: string;
572
+ machineFingerprint: string;
573
+ label?: string;
574
+ }): Promise<LicenseVerifyResultDto>;
575
+ }
576
+ declare class UsageClient {
577
+ private readonly client;
578
+ constructor(client: Rekey);
579
+ /**
580
+ * Record a usage event against a named meter. `quantity` can be
581
+ * negative to credit back (e.g. refunds). `occurredAt` defaults to
582
+ * server time; pass an ISO string when ingesting historical events.
583
+ */
584
+ record(input: {
585
+ meterSlug: string;
586
+ quantity: number;
587
+ /** Attribute to an end-user, or an `organizationId` (shared org pool), or
588
+ * neither (app-level usage). Pass at most one subject. */
589
+ endUserId?: string;
590
+ organizationId?: string;
591
+ occurredAt?: string;
592
+ metadata?: Record<string, unknown>;
593
+ }): Promise<UsageRecordDto>;
594
+ /**
595
+ * Sum recorded quantity for a meter, optionally bounded by a time window
596
+ * and/or scoped to a subject (`endUserId` or `organizationId`). Drives
597
+ * "you've used X of your Y quota" displays.
598
+ */
599
+ aggregate(input: {
600
+ meterSlug: string;
601
+ from?: string;
602
+ to?: string;
603
+ endUserId?: string;
604
+ organizationId?: string;
605
+ }): Promise<UsageAggregateDto>;
606
+ }
607
+ /**
608
+ * Prepaid credits — the "lead pack" / pay-as-you-go drawdown model. The
609
+ * customer's backend grants credits (by selling a CREDIT-kind plan, which
610
+ * grants automatically on payment) and draws them down per unit consumed.
611
+ *
612
+ * All calls are server-to-server (secret key) and scoped to an end-user id.
613
+ */
614
+ /**
615
+ * A credit subject — pass `endUserId` for a personal balance, or
616
+ * `organizationId` for a shared org pool (owner+beneficiary billing).
617
+ */
618
+ export type CreditSubject = {
619
+ endUserId: string;
620
+ } | {
621
+ organizationId: string;
622
+ };
623
+ declare class CreditsClient {
624
+ private readonly client;
625
+ constructor(client: Rekey);
626
+ /** Current spendable balance for a subject (end-user or org); 0 if none. */
627
+ getBalance(subject: CreditSubject): Promise<CreditBalanceDto>;
628
+ /**
629
+ * Deduct credits from a subject (end-user or org pool). Throws `RekeyError`
630
+ * `code: "CREDITS_INSUFFICIENT"` (HTTP 402) when the balance is too low.
631
+ *
632
+ * Pass `idempotencyKey` (e.g. the lead id) so a retried call never
633
+ * double-charges — a repeat returns the original result with `applied: false`.
634
+ */
635
+ consume(input: ConsumeCreditsRequest & CreditSubject): Promise<ConsumeCreditsResultDto>;
636
+ /**
637
+ * Ledger entries for a subject, newest first. Pass `offset` to page back
638
+ * through the full append-only history (the ledger grows for the life of a
639
+ * subject); `limit` is capped at 200 server-side.
640
+ */
641
+ listLedger(subject: CreditSubject, limit?: number, offset?: number): Promise<CreditLedgerEntryDto[]>;
642
+ }
643
+ /** What an end-user (or org) is entitled to right now — from active subs. */
644
+ export interface EntitlementsDto {
645
+ /** Feature flags + numeric limits, keyed by code. Gate your app on these. */
646
+ features: Record<string, boolean | number | string>;
647
+ /** The raw resolved entitlement rows (all kinds). */
648
+ entitlements: Array<{
649
+ kind: 'FEATURE' | 'CREDIT' | 'LICENSE' | 'USAGE';
650
+ key: string;
651
+ valueType: 'BOOL' | 'INT' | 'STRING' | null;
652
+ value: string | null;
653
+ quantity: number | null;
654
+ licenseKind: 'PERPETUAL' | 'TIMED' | 'SEATS' | null;
655
+ rollover: boolean;
656
+ }>;
657
+ /** Live credit balance for the resolved subject. */
658
+ creditBalance: number;
659
+ }
660
+ /**
661
+ * Verify the HMAC signature on an inbound webhook from Rekey. Returns
662
+ * `true` only when (a) the timestamp is fresh (within `toleranceSeconds`,
663
+ * default 300) AND (b) the signature matches a constant-time compare.
664
+ *
665
+ * Use against the `X-Rekey-Signature` header and the raw request body
666
+ * BYTES (not the parsed JSON — any reserialization breaks the HMAC).
667
+ *
668
+ * @example
669
+ * ```ts
670
+ * import { verifyWebhookSignature } from '@rekey.dev/node';
671
+ *
672
+ * app.post('/webhooks/rekey', { config: { rawBody: true } }, (req) => {
673
+ * const ok = verifyWebhookSignature({
674
+ * header: req.headers['x-rekey-signature'] as string,
675
+ * payload: req.rawBody!,
676
+ * secret: process.env.RELIPAY_WEBHOOK_SECRET!,
677
+ * });
678
+ * if (!ok) return reply.status(401).send({ error: 'bad signature' });
679
+ * // safe to act on req.body
680
+ * });
681
+ * ```
682
+ */
683
+ export declare function verifyWebhookSignature(args: {
684
+ header: string | null | undefined;
685
+ payload: string | Buffer;
686
+ secret: string;
687
+ toleranceSeconds?: number;
688
+ now?: () => number;
689
+ }): boolean;
690
+ /** Verified claims of an RS256 end-user access token. */
691
+ export interface VerifiedAccessTokenClaims {
692
+ /** Always `"eu_access"` — other token types are refused. */
693
+ typ: 'eu_access';
694
+ /** EndUser id. */
695
+ sub: string;
696
+ /** Application the token is bound to — check it against YOUR application id. */
697
+ applicationId: string;
698
+ /** Active organization id, when the session is acting as an org. */
699
+ oid?: string;
700
+ /** Operator id when this is an impersonation session (treat with care). */
701
+ imp?: string;
702
+ /** App `tokenGeneration` at mint time (the API checks this; offline can't). */
703
+ gen?: number;
704
+ iat: number;
705
+ exp: number;
706
+ }
707
+ export interface VerifyAccessTokenOptions {
708
+ /**
709
+ * URL of the deployment's JWKS — `https://<your-rekey>/.well-known/jwks.json`.
710
+ * Fetched lazily and cached in-process for `cacheTtlMs` (default 5 minutes);
711
+ * an unknown `kid` triggers one immediate refetch so freshly rotated keys
712
+ * are picked up without waiting out the TTL.
713
+ */
714
+ jwksUrl?: string;
715
+ /** Pre-fetched key set — skips all network access. Takes precedence over `jwksUrl`. */
716
+ jwks?: JwksDto;
717
+ /** Optional fetch override (test stubs, custom agents). */
718
+ fetch?: typeof fetch;
719
+ /** JWKS cache lifetime in ms when using `jwksUrl`. Default 300 000 (5 min). */
720
+ cacheTtlMs?: number;
721
+ /** Clock override for tests. Returns ms since epoch. */
722
+ now?: () => number;
723
+ }
724
+ /** @internal Test hook — drop cached JWKS responses. */
725
+ export declare function _clearJwksCacheForTests(): void;
726
+ /**
727
+ * Verify an end-user ACCESS token **offline** — no round-trip to the Rekey
728
+ * API. Works only for Applications that opted into RS256 tokens
729
+ * (`authConfig.tokenAlg = "RS256"`, Panel → Application → Auth); the default
730
+ * HS256 tokens are symmetric and can only be verified by the API itself
731
+ * (use `rekey.auth.getCurrentUser(token)` for those).
732
+ *
733
+ * Checks performed (same posture as the API's verifier):
734
+ * - header `alg` must be `RS256` and `kid` must exist in the JWKS —
735
+ * a strict allowlist, immune to alg-confusion;
736
+ * - RSA-SHA256 signature against that public key;
737
+ * - `exp` in the future, `typ === "eu_access"` (refresh/MFA/MCP tokens
738
+ * are refused), `sub` + `applicationId` present.
739
+ *
740
+ * What it CANNOT check offline: the app's `tokenGeneration` kill-switch and
741
+ * user deletion. The 15-minute access lifetime bounds both; for hard
742
+ * revocation guarantees keep using `auth.getCurrentUser`.
743
+ *
744
+ * Node-only (uses `node:crypto`). Returns the verified claims; throws
745
+ * `RekeyError` on any failure.
746
+ *
747
+ * @example Express/Fastify middleware at the edge
748
+ * ```ts
749
+ * import { verifyAccessToken } from '@rekey.dev/node';
750
+ *
751
+ * const claims = await verifyAccessToken(req.headers['x-rekey-user-token'], {
752
+ * jwksUrl: 'https://rekey.example.com/.well-known/jwks.json',
753
+ * });
754
+ * if (claims.applicationId !== MY_APP_ID) throw new Error('wrong app');
755
+ * req.userId = claims.sub;
756
+ * ```
757
+ *
758
+ * @throws {RekeyError} `TOKEN_ALG_NOT_RS256` — token is HS256 (app hasn't opted in) or another alg.
759
+ * @throws {RekeyError} `TOKEN_KID_UNKNOWN` — `kid` not in the JWKS (forged, or key deleted).
760
+ * @throws {RekeyError} `USER_TOKEN_EXPIRED` — `exp` passed; refresh the session.
761
+ * @throws {RekeyError} `USER_TOKEN_INVALID` — malformed, bad signature, or wrong `typ`.
762
+ */
763
+ export declare function verifyAccessToken(token: string, options: VerifyAccessTokenOptions): Promise<VerifiedAccessTokenClaims>;
764
+ declare class BillingClient {
765
+ private readonly client;
766
+ constructor(client: Rekey);
767
+ /**
768
+ * List the calling Application's active plans. Public — pricing pages
769
+ * typically render straight from this. Application API key only; no
770
+ * user JWT needed.
771
+ *
772
+ * `amount` is in the smallest currency unit (cents/paise/sen) — never
773
+ * a float. Format on display: `${amount / 100} ${currency}`.
774
+ */
775
+ getPlans(): Promise<PlanDto[]>;
776
+ /**
777
+ * Fetch the current end-user's active subscription, or `null` if they
778
+ * have none. Returns the most recent ACTIVE / PENDING / PAST_DUE row.
779
+ *
780
+ * Pass the user's access token (the SDK puts it in `X-Rekey-User-Token`).
781
+ */
782
+ getSubscription(accessToken: string): Promise<SubscriptionDto | null>;
783
+ /**
784
+ * Start a hosted-checkout session. Returns the URL to redirect the user
785
+ * to and the local PENDING Subscription row. Subscription activation
786
+ * happens via the provider's webhook — not synchronously here.
787
+ *
788
+ * Pass `couponCode` to apply a discount. The whole checkout fails if the
789
+ * coupon doesn't validate (typed `RekeyError` with the precise reason).
790
+ *
791
+ * If the Application's billing subject is **org** (Panel → Application →
792
+ * Billing → Subject), an individual can't hold a subscription — you MUST
793
+ * pass `organizationId` of a team the user owns/admins. Omitting it throws
794
+ * `RekeyError` `code: "BILLING_ORGANIZATION_REQUIRED"`.
795
+ *
796
+ * @example
797
+ * ```ts
798
+ * const { url, discountAmount } = await rekey.billing.createCheckout(userAccessToken, {
799
+ * planSlug: 'pro_monthly',
800
+ * successUrl: 'https://yourapp.com/billing?status=ok',
801
+ * cancelUrl: 'https://yourapp.com/billing?status=cancel',
802
+ * couponCode: 'LAUNCH50', // optional
803
+ * });
804
+ * res.redirect(url);
805
+ * ```
806
+ */
807
+ createCheckout(accessToken: string, input: CreateCheckoutRequest & {
808
+ couponCode?: string;
809
+ }): Promise<CheckoutResultDto>;
810
+ /**
811
+ * Validate a coupon for the current user against a plan, *without*
812
+ * applying it. Render "$50 off" on a pricing page before submit.
813
+ *
814
+ * @throws {RekeyError} with one of `COUPON_NOT_FOUND` / `COUPON_INACTIVE`
815
+ * / `COUPON_NOT_YET_STARTED` / `COUPON_EXPIRED` / `COUPON_NOT_APPLICABLE`
816
+ * / `COUPON_CURRENCY_MISMATCH` / `COUPON_REDEMPTION_LIMIT_REACHED` /
817
+ * `COUPON_USER_LIMIT_REACHED`. Surface the message + fix to the user.
818
+ */
819
+ validateCoupon(accessToken: string, input: ValidateCouponRequest): Promise<ValidateCouponResultDto>;
820
+ /**
821
+ * List the billing providers configured + enabled for this Application,
822
+ * in the order the geo router would prefer them. Forward the end-user's
823
+ * `country` (ISO 3166-1 alpha-2) when you have it — the panel/SDK will
824
+ * surface India-specific providers (Razorpay) for IN-country users, etc.
825
+ *
826
+ * Returns the resolved country (echoed back from the server's view of
827
+ * `CF-IPCountry` etc.) plus the ordered provider list. Use this to render
828
+ * a "Pay with..." picker on your pricing page.
829
+ */
830
+ getProviders(country?: string): Promise<ProvidersListDto>;
831
+ /**
832
+ * Resolve the calling end-user's current entitlements — feature flags +
833
+ * limits, the live credit balance, and the raw entitlement list, unioned
834
+ * across their active subscriptions (and subscriptions of orgs they belong
835
+ * to). Pass `{ organizationId }` (member-only) for that org's view + shared
836
+ * pool. Gate your app's features on `features`.
837
+ *
838
+ * @example
839
+ * ```ts
840
+ * const { features } = await rekey.billing.getEntitlements(userAccessToken);
841
+ * if (features.advanced_reporting) renderReportingTab();
842
+ * ```
843
+ */
844
+ getEntitlements(accessToken: string, opts?: {
845
+ organizationId?: string;
846
+ }): Promise<EntitlementsDto>;
847
+ }
848
+ //# sourceMappingURL=index.d.ts.map