SDK Reference
ReferenceGenerated from the SDKs' TypeScript types. New to Rekey? The quickstart takes a Next.js app to a working sign-in first, and Configure below names every environment variable. For the HTTP endpoints, see the API reference.
Configure
Every environment variable the SDKs read, checked against their source. Anything marked server only must stay out of browser code, so never give REKEY_SECRET a NEXT_PUBLIC_ or PUBLIC_ name. For where each value comes from, step by step, see the quickstart.
@rekey.dev/nextjs
The server helpers read REKEY_URL and REKEY_SECRET. The two NEXT_PUBLIC_ names are read only by rekeyBrowser() from @rekey.dev/nextjs/client.
REKEY_URLServer only, required- The API origin. https://api.rekey.dev on Rekey Cloud; self-hosting, your own deployment's public origin (http://localhost:3030 locally). There is no default.
REKEY_SECRETServer only, required- The Application's secret key, rp_test_… or rp_live_…, from Application → Developer → API keys. Never give it a NEXT_PUBLIC_ or PUBLIC_ name.
NEXT_PUBLIC_REKEY_URLBrowser safe, optional- The same origin as REKEY_URL, for signing in from a client component with rekeyBrowser().
NEXT_PUBLIC_REKEY_PUBLIC_KEYBrowser safe, optional- The publishable key, rp_pub_…, from the Publishable key card on the API keys page. Also read only by rekeyBrowser().
REKEY_COOKIE_SECUREServer only, optional- true or false. Leave it unset: the SDK decides per request from X-Forwarded-Proto and the host. Set false only to serve a real hostname over plain HTTP.
REKEY_TRUSTED_PROXY_HOPSServer only, optional- How many proxies in front of the app append to X-Forwarded-For, so sign-in forwards the visitor's real IP to the API's per-IP limits. Default 1. Set 0 when nothing sits in front of the app.
@rekey.dev/astro
Read from process.env at request time. Vite loads .env for astro dev only, so in production pass them through the host or process manager.
REKEY_URLServer only, required- The API origin. https://api.rekey.dev on Rekey Cloud; self-hosting, your own deployment's public origin (http://localhost:3030 locally). There is no default.
REKEY_SECRETServer only, required- The Application's secret key, rp_test_… or rp_live_…, from Application → Developer → API keys. Never give it a NEXT_PUBLIC_ or PUBLIC_ name.
REKEY_COOKIE_SECUREServer only, optional- true or false. Leave it unset: the SDK decides per request from X-Forwarded-Proto and the host. Set false only to serve a real hostname over plain HTTP.
@rekey.dev/node
Reads nothing from the environment. Pass the values in yourself: new Rekey({ apiUrl: process.env.REKEY_URL, secretKey: process.env.REKEY_SECRET }). The names are the convention the other SDKs read.
@rekey.dev/react
Reads nothing from the environment. <RekeyProvider> takes apiUrl, and optionally publishableKey, as props. It never takes the secret key.
@rekey.dev/node
pnpm add @rekey.dev/nodeServer SDK. One client per Application, constructed with the secret key. Auth, billing, organizations, licenses, usage, and credits live as namespaces on the client.
RekeyTop-level Rekey client. Auth and billing live as namespaces (`rekey.applications`, `rekey.auth`, `rekey.billing`) so an agent reading `rekey.` in an editor sees a discoverable surface.
new Rekey(config: RekeyConfig)with(options: RekeyCallOptions): RekeyA clone of this client with different call options, the per-call knob for every wrapped method.
request(method: string, path: string, options?: RekeyRequestOptions): Promise<T>Call a Rekey endpoint this SDK does not wrap yet.
ListsClientLists: a waitlist, a newsletter or a contact form that stores people who have no account. Create lists in the panel (Lists), then subscribe people from your server. Rekey sends no email to a list: read the members out with {@link ListsClient.members} and hand them to your email tool.
new ListsClient(private readonly client: Rekey)list(): Promise<ContactListSummaryDto[]>Every list of this Application with its member counts. Archived lists are included and flagged. Needs `contacts:write`, which `*` keys hold.
get(key: string): Promise<ContactListPublicDto>A list's form: its extra fields, and the consent text and version to show.
subscribe(key: string, input: ListSubscribeRequest, options: ListSubscribeOptions = {}): Promise<ListSubscribeOutcome | ListSubscribeReceived>Add someone to a list. Needs `contacts:write`, which `*` keys hold.
members(key: string, options: ListMembersOptions = {}): Promise<ListMembersPage>One page of a list's members, oldest change first. Needs a key minted with the elevated `contacts:read` scope; `*` does not include it.
iterateMembers(key: string, options: Omit<ListMembersOptions, 'cursor'> = {}): AsyncGenerator<ListMemberDto>Every member, following cursors for you. Store the last `updatedAt` you see and pass it as `updatedSince` next time to sync only what changed.
unsubscribe(key: string, email: string): Promise<{ status: 'unsubscribed' | 'not_subscribed' }>Take someone off a list, for example when your email tool reports an unsubscribe. Idempotent. Needs `contacts:write`.
rekey.applicationsApplicationsClientme(): Promise<ApplicationDto>Verify credentials and fetch the calling Application. Use this as your SDK smoke test, if it returns, your secret key is good and you're pointed at the right Rekey deployment.
rekey.authAuthClientsignUp(input: SignUpRequest): Promise<AuthResultDto>Create a new end-user in the calling Application via email + password. Returns the user plus an `accessToken` for subsequent per-user calls (e.g. `getCurrentUser(accessToken)`) and a `refreshToken` to renew it.
signIn(input: SignInRequest): Promise<SignInOutcomeDto>Authenticate an existing end-user with email + password.
mfaVerify(input: MfaVerifyRequest): Promise<AuthResultDto>Exchange an MFA challenge token + TOTP/backup code for a real session. Use after `signIn` (or OAuth callback) returns `mfaRequired: true`.
requestMagicLink(input: { email: string; signInUrl?: string; }): Promise<{ delivered: boolean; emailSent: boolean; magicLinkToken: string | null; }>Request a magic-link sign-in email. Enumeration-safe: same response shape whether the email exists or not. When the Application has email transport configured, the link is sent and `magicLinkToken` is null; otherwise the raw token is returned for you to forward.
verifyMagicLink(input: { token: string; /** Bind the session to a device, see docs/devices.md. */ device?: { fingerprint: string; label?: string }; }): Promise<SignInOutcomeDto>Consume a magic-link token. Returns `SignInOutcome`, branch on `mfaRequired` before reading `accessToken`. For MFA-enrolled users the response carries `mfaChallengeToken` and you must complete via `mfaVerify(...)`. `isNewUser` is true when this link created the account.
startPasskeyAuthentication(input?: { email?: string }): Promise<{ options: unknown; expectedChallenge: string; }>Begin a passkey authentication ceremony. Returns the WebAuthn options to forward to the browser (`navigator.credentials.get(...)`) along with `expectedChallenge`, bind the challenge to your session and pass both back via `verifyPasskeyAuthentication(...)`.
verifyPasskeyAuthentication(input: { response: unknown; expectedChallenge: string; /** Bind the session to a device, see docs/devices.md. */ device?: { fingerprint: string; label?: string }; }): Promise<SignInOutcomeDto>Complete a passkey authentication. Returns the same `SignInOutcome` shape as `signIn`, but passkeys are themselves a strong factor, so `mfaRequired` will always be `false` in practice.
startPasskeyRegistration(accessToken: string): Promise<{ options: unknown; expectedChallenge: string; }>Begin a passkey registration ceremony for an authenticated user. Forward `options` to `navigator.credentials.create(...)`; store `expectedChallenge` in session; POST both back via `verifyPasskeyRegistration(...)`.
verifyPasskeyRegistration(accessToken: string, input: { response: unknown; expectedChallenge: string; deviceName?: string; }): Promise<{ credentialId: string; deviceName: string | null }>listPasskeys(accessToken: string, page?: ListPage): Promise< Paged<{ id: string; credentialId: string; deviceName: string | null; lastUsedAt: string | null; createdAt: string; }> >List the user's registered passkeys, newest first.
deletePasskey(accessToken: string, credentialRowId: string): Promise<{ deleted: boolean }>Remove a passkey. Returns `{deleted: false}` if the row doesn't belong to this user.
getCurrentUser(accessToken: string, options: { include?: L } = {}): Promise<CurrentUserDto & MeIncludedFor<L>>Resolve the end-user behind a presented access token.
updateCurrentUser(accessToken: string, input: { metadata?: Record<string, unknown> | null }): Promise<CurrentUserDto>Update the end-user behind a presented access token, their OWN record, and only ever their own: the token identifies the subject, so there is no user id to pass and no way to aim this at anyone else.
deleteCurrentUser(accessToken: string): Promise<{ erased: true; erasedAt: string }>Erase the signed-in user's own account, as an operator erasure does. It cannot be undone. With a secret key no confirmation is needed; your server is the gate. Throws ACCOUNT_HAS_ACTIVE_SUBSCRIPTION (409) while a subscription still bills through a provider: cancel it first, and ACCOUNT_SELF_DELETION_DISABLED (403) when the Application sets `authConfig.accountDeletion` to `off`.
refresh(refreshToken: string, options: { device?: DeviceBindingRequest } = {}): Promise<AuthResultDto>Exchange a refresh token for a fresh {access, refresh} pair. The presented refresh is revoked atomically, call this **once** and store the new `refreshToken` from the response immediately.
signOut(refreshToken: string): Promise<{ signedOut: true }>Revoke a refresh token. Idempotent, no-op for unknown tokens. The access token paired with this refresh remains valid until its short (15 min) expiry; for true "log out everywhere" semantics, also clear the access token from your client.
requestPasswordReset(input: ForgotPasswordRequest): Promise<ForgotPasswordResultDto>Request a password reset for an email. Always succeeds, never tells you whether the email exists.
resetPassword(input: ResetPasswordRequest): Promise<{ ok: true }>Consume a reset token + set a new password. Single-use. On success, every refresh token for the user is revoked.
changePassword(accessToken: string, input: ChangePasswordRequest): Promise<{ ok: true }>Authenticated password change. Pass the user's *current* access token. On success, every refresh token for the user is revoked, other devices are signed out.
signOutEverywhere(accessToken: string): Promise<{ revokedCount: number }>Revoke every refresh token for the calling user. "Sign out of all devices." The caller's access token remains valid until 15-min expiry, clear it client-side for full logout.
sendVerificationEmail(accessToken: string, input?: { verifyUrl?: string }): Promise<{ emailSent: boolean; verificationToken: string | null }>Send (or re-send) an email-verification link to the current user. If email transport is configured on the Application, Rekey sends the email and `verificationToken` is null. Otherwise the raw token is returned for the caller to forward via their own provider.
resendVerificationEmail(input: { email: string; verifyUrl?: string; }): Promise<{ emailSent: boolean; verificationToken: string | null }>Re-send a verification link to an address, with **no session**, the sessionless sibling of `sendVerificationEmail`.
verifyEmail(input: { token: string }): Promise<{ verified: true; endUser: EndUserDto }>Consume an email-verification token. Single-use, 24-hour lifetime. Marks `emailVerified: true` on the user record. Cross-Application tokens are refused with `EMAIL_VERIFICATION_TOKEN_WRONG_APPLICATION`.
listSessions(accessToken: string, page?: ListPage): Promise< Paged<{ id: string; createdAt: string; expiresAt: string; userAgent: string | null; ip: string | null; }> >List the current user's active sessions (live refresh tokens), newest first. Each carries the User-Agent + IP captured at issue time and an `id` you can pass to `revokeSession(...)`.
revokeSession(accessToken: string, sessionId: string): Promise<{ revoked: boolean }>Revoke one session by id. Idempotent, `{ revoked: false }` if it isn't this user's.
mfaStatus(accessToken: string): Promise<{ enabled: boolean; remainingBackupCodes: number | null; policy: 'off' | 'optional' | 'required'; }>MFA enrollment status for the current user, plus the Application's policy.
mfaSetup(accessToken: string): Promise<{ otpauthUrl: string; backupCodes: string[]; warning: string; }>Begin TOTP enrollment: mints a secret (as an `otpauthUrl` for the QR) and 10 single-show backup codes. **Not enrolled until `confirmMfaSetup(...)`.** Only SHA-256 hashes of the backup codes are stored, show them once.
confirmMfaSetup(accessToken: string, code: string): Promise<{ ok: true }>Confirm enrollment by submitting the current 6-digit TOTP code.
mfaChallenge(accessToken: string, code: string): Promise<{ ok: boolean }>Verify a TOTP or backup code as a step-up check (does NOT issue a session). Backup codes are single-use, consumed on success. Returns `{ ok }`.
disableMfa(accessToken: string): Promise<{ disabled: true }>Disable MFA for the current user.
listOAuthProviders(): Promise<OAuthProvidersListDto>The OAuth providers this Application can sign users in with, one entry per button a sign-in page should render. Only providers configured with both a client id and a client secret are listed, so every entry can be started. Returns ids and display names only, never client ids or secrets.
startOAuth(provider: string, state: string): Promise<{ authorizationUrl: string }>Get the provider authorization URL to redirect the browser to. Pass an unguessable `state` and verify it on return before calling `completeOAuth`.
completeOAuth(provider: string, code: string, options?: { /** Bind the session to a device, see docs/devices.md. */ device?: { fingerprint: string; label?: string }; }): Promise<SignInOutcomeDto>Exchange the provider `code` for a Rekey session. Returns a `SignInOutcome`, branch on `mfaRequired` before reading `accessToken`. Verify the `state` CSRF value yourself before calling.
listOAuthIdentities(accessToken: string): Promise< Array<{ provider: string; providerAccountId: string; email: string | null; createdAt: string; }> >List the OAuth providers linked to the current user.
startOAuthLink(accessToken: string, provider: string, state: string): Promise<{ authorizationUrl: string }>Begin linking a provider to the *currently authenticated* user.
completeOAuthLink(accessToken: string, provider: string, code: string): Promise<{ provider: string; providerAccountId: string; alreadyLinked: boolean }>Complete an OAuth link, attaches the provider identity to the current user. Refuses on unverified provider emails (account-takeover guard) or when the provider account already belongs to a different user.
unlinkOAuth(accessToken: string, provider: string): Promise<{ unlinked: boolean }>Remove a linked provider. Refuses with `OAUTH_UNLINK_WOULD_LOCK_OUT` (409) if it would leave the account with no way to sign in.
rekey.billingBillingClientgetPlans(page?: ListPage, opts?: { country?: string; accessToken?: string }): Promise<Paged<PublicPlanDto>>List the calling Application's active plans. Public, pricing pages typically render straight from this. Application API key only; no user JWT needed.
getSubscription(accessToken: string, opts?: { organizationId?: string; includeEnded?: boolean }): Promise<SelfSubscriptionDto | null>Fetch the current end-user's active subscription, or `null` if they have none. Returns the most recent ACTIVE / PENDING / PAST_DUE row.
listSubscriptions(accessToken: string, opts?: { organizationId?: string }): Promise<{ items: SelfSubscriptionDto[] }>Every live (ACTIVE, TRIALING, PAST_DUE) subscription of the user, or of `opts.organizationId` (member-only), with the one {@link getSubscription} returns first. Check it before offering a checkout, so a buyer is never offered a plan they already hold.
createCheckout(accessToken: string, input: CreateCheckoutRequest & { couponCode?: string }): Promise<CheckoutResultDto>Start a hosted-checkout session. Returns the URL to redirect the user to and the local PENDING Subscription row. Subscription activation happens via the provider's webhook, not synchronously here.
subscribe(accessToken: string, input: { organizationId?: string } = {}): Promise<{ subscription: SelfSubscriptionDto | null; activated: boolean }>Put the calling end-user on the Application's free tier (`billingConfig.defaultPlanSlug`). No payment provider is involved and none needs to be configured: the plan costs nothing.
getTrialEligibility(accessToken: string, opts?: { organizationId?: string; planSlug?: string; limit?: number; offset?: number; country?: string; }): Promise<TrialEligibilityDto>Whether THIS buyer may start each plan's free trial, under the Application's `trialPolicy`.
validateCoupon(accessToken: string, input: ValidateCouponRequest): Promise<ValidateCouponResultDto>Validate a coupon for the current user against a plan, *without* applying it. The quote is made at the price the checkout would charge: render `formatted.discount` and `formatted.total`, which are written in that price's currency.
getProviders(country?: string, opts?: { planSlug?: string }): Promise<ProvidersListDto>List the billing providers enabled for this Application, in the order the router prefers them for the buyer's country. `routedProvider` is the one a checkout that names no provider is sent to.
getEntitlements(accessToken: string, opts?: { organizationId?: string }): Promise<EntitlementsDto>Resolve the calling end-user's current entitlements, feature flags + limits, the live credit balance, and the raw entitlement list, unioned across their active subscriptions (and subscriptions of orgs they belong to). Pass `{ organizationId }` (member-only) for that org's view + shared pool. Gate your app's features on `features`.
getEntitlementsFor(endUserId: string, opts?: { organizationId?: string }): Promise<EntitlementsDto>The same union as `getEntitlements`, for an end-user you name rather than one whose token you hold. Secret key only, for a licence server, a support tool or a batch job.
getFeature(accessToken: string, key: string, opts?: { organizationId?: string }): Promise<FeatureCheckDto>One feature for the calling end-user: `{ key, granted, value }`, where `value` is what `features[key]` holds in `getEntitlements` (null when nothing grants it) and `granted` is `Boolean(value)`. The subject is the one `getCurrentUser(token, { include: ['entitlements'] })` resolves, or the organization you pass (member-only).
hasFeature(accessToken: string, key: string, opts?: { organizationId?: string }): Promise<boolean>Whether the calling end-user holds a feature: `Boolean(value)`, the test `if (features[key])` makes, so a false flag, a 0 limit and an unknown key are all `false`. Use `getFeature` to read a numeric limit.
getFeatureFor(endUserId: string, key: string, opts?: { organizationId?: string }): Promise<FeatureCheckDto>`getFeature` for an end-user you name. Secret key only, like `getEntitlementsFor`.
hasFeatureFor(endUserId: string, key: string, opts?: { organizationId?: string }): Promise<boolean>`hasFeature` for an end-user you name. Secret key only.
cancelSubscription(accessToken: string, input?: { atPeriodEnd?: boolean; organizationId?: string; subscriptionId?: string; reason?: CancelReason; feedback?: string; }): Promise<SelfSubscriptionDto>Cancel the calling end-user's current subscription.
rekey.creditsCreditsClientPrepaid credits, the "lead pack" / pay-as-you-go drawdown model. The customer's backend grants credits (by selling a CREDIT-kind plan, which grants automatically on payment) and draws them down per unit consumed.
getBalance(subject: CreditSubject): Promise<CreditBalanceDto>Current spendable balance for a subject (end-user or org); 0 if none.
consume(input: ConsumeCreditsRequest & CreditSubject): Promise<ConsumeCreditsResultDto>Deduct credits from a subject (end-user or org pool). Throws `RekeyError` `code: "CREDITS_INSUFFICIENT"` (HTTP 402) when the balance is too low.
listLedger(subject: CreditSubject, limit?: number, offset?: number): Promise<Paged<CreditLedgerEntryDto>>Ledger entries for a subject, newest first. Pass `offset` to page back through the full append-only history (the ledger grows for the life of a subject); `limit` is capped at 200 server-side.
grant(input: KeyGrantCreditsRequest): Promise<ConsumeCreditsResultDto>Grant credits to an end-user or organization pool with the Application key. Needs a key minted with the elevated `credits:grant` scope named: `*` does not include it, so a default key gets 403 `API_KEY_SCOPE_INSUFFICIENT`.
listMyLedger(accessToken: string, opts?: { organizationId?: string; limit?: number; offset?: number }): Promise<Paged<SelfCreditLedgerEntryDto>>The signed-in end-user's own credit ledger, newest first (the active organization's pool in an Application that bills organizations, or the one `{ organizationId }` names, member-only). Entries carry no `metadata`.
rekey.devicesDevicesClientEnd-users' devices (docs/devices.md), both surfaces.
list(endUserId: string, options: { status?: DeviceStatusType; limit?: number; offset?: number } = {}): Promise<Paged<DeviceDto>>An end-user's devices, newest activity first. Optional `status` filter.
release(deviceId: string, endUserId: string): Promise<{ device: DeviceDto; sessionsRevoked: number }>Release a device: gives its slot back and revokes every session on it.
listMine(accessToken: string, options: { status?: DeviceStatusType; limit?: number; offset?: number } = {}): Promise<Paged<EndUserDeviceDto>>The calling end-user's OWN devices, newest activity first.
releaseMine(accessToken: string, deviceId: string): Promise<{ device: EndUserDeviceDto; sessionsRevoked: number }>Release one of the calling end-user's own devices.
rekey.emailEmailClientsend(input: Omit<EmailSendRequest, 'dryRun'>): Promise<EmailSendResult>Send a custom template that was registered and published in the panel (Application → Email → Custom templates). The call names the template and passes variables; the subject, body and From address come from the published version, and the mail goes out through the Application's own Resend or SMTP provider, never a shared pool.
dryRun(input: Omit<EmailSendRequest, 'dryRun' | 'idempotencyKey'>): Promise<EmailDryRunResult>Run every check a send runs and render the template, without sending: no log row, no cap counted, no idempotency key used. `status: 'suppressed'` means a real send would not go out. Use it in CI to catch a template change that breaks your variables.
cancelSend(id: string): Promise<EmailSendRecord>Cancel a send made with `sendAt` before it fires. Throws EMAIL_SEND_NOT_SCHEDULED once it has fired or was canceled.
getSend(by: { id: string } | { idempotencyKey: string }): Promise<EmailSendRecord>rekey.licensesLicensesClientverify(input: { key: string; machineFingerprint: string; label?: string; }): Promise<LicenseVerifyResultDto>Verify a license key + record an activation for this machine. Call once at app startup; you'll get a deterministic body (`ok=false` for invalid licenses, never an HTTP error, so your software can loop on the result without try/catch noise).
deactivate(input: LicenseDeactivateRequest): Promise<LicenseDeactivateResultDto>Give back the seat this machine holds; call it before a re-image or on uninstall so the next machine can verify. Same deterministic body as `verify`; `released: false` means the machine held no seat.
listMine(accessToken: string, page?: ListPage, opts?: { organizationId?: string }): Promise<Paged<EndUserLicenseDto>>The signed-in end-user's own licences, newest first: `GET /api/v1/users/me/licenses`, authorized by their access token. In an org-billed Application whose session acts for an organization, that organization's pooled licences are included too. Pass `{ organizationId }` to include that organization's pool instead; the caller must be a member, or the API answers 403 `ORGANIZATION_NOT_MEMBER`.
rekey.mcpMcpClientMCP helpers for customers running their OWN MCP server behind Rekey auth. The hosted MCP server (account tools) is consumed by MCP clients directly, this client is for the "bring your own MCP server" path: validate incoming Rekey-issued tokens, and read the OAuth metadata.
introspect(token: string): Promise<OAuthIntrospectionResponse>Validate an MCP access token (RFC 7662 introspection). Call this from your own MCP server to authorize an incoming request. Authenticated with this client's secret key.
metadata(): Promise<OAuthAuthServerMetadata>Fetch this application's OAuth authorization-server metadata (RFC 8414).
rekey.organizationsOrganizationsClientcreate(accessToken: string, input: { name: string; slug: string; metadata?: Record<string, unknown> }): Promise<{ organization: OrganizationDto; membership: { id: string; role: 'OWNER'; baseRole: 'OWNER' }; }>Create an organization; the calling user becomes the OWNER.
listRoles(accessToken: string): Promise<OrganizationRoleDefDto[]>List the organization roles assignable in this Application.
listMine(accessToken: string, page?: ListPage): Promise<Paged<OrganizationWithRoleDto>>List organizations the calling user belongs to, with their role.
get(accessToken: string, organizationId: string): Promise<OrganizationWithRoleDto>Fetch one organization the caller belongs to.
update(accessToken: string, organizationId: string, input: { name?: string; metadata?: Record<string, unknown> }): Promise<OrganizationDto>Update org name / metadata. OWNER + ADMIN only.
listMembers(accessToken: string, organizationId: string, page?: ListPage): Promise<Paged<OrganizationMemberDto>>List members of an organization the caller belongs to.
invite(accessToken: string, organizationId: string, input: { email: string; role?: OrganizationRole }): Promise<{ invitation: OrganizationInvitationDto; token: string }>Invite a user. Returns the raw token ONCE, surface via your own email/share channel. OWNER + ADMIN only.
revokeInvitation(accessToken: string, organizationId: string, invitationId: string): Promise<{ revoked: boolean }>Revoke a pending invitation. OWNER + ADMIN only. Idempotent.
setMemberRole(accessToken: string, organizationId: string, targetEndUserId: string, input: { role: OrganizationRole }): Promise<{ id: string; organizationId: string; endUserId: string; role: OrganizationRole; /** The tier the name maps to. Gate your own features on this. */ baseRole: OrganizationBaseRole; }>Change a member's role. OWNER manages anyone; ADMIN manages MEMBER only. Last-OWNER guard refuses demoting the only OWNER.
removeMember(accessToken: string, organizationId: string, targetEndUserId: string): Promise<{ removed: boolean }>Remove a member (or self). Refuses removing the last OWNER.
leave(accessToken: string, organizationId: string): Promise<{ removed: boolean }>Self-leave. An OWNER cannot leave (payment + benefits are tied to the owner, `ORGANIZATION_OWNER_CANNOT_LEAVE`); transfer ownership via support first, or demote yourself to ADMIN if there is another OWNER.
acceptInvitation(accessToken: string, input: { token: string }): Promise<{ membership: { id: string; organizationId: string; role: 'OWNER' | 'ADMIN' | 'MEMBER'; }; }>Accept an organization invitation by raw token. Refuses cross- Application invitations. Idempotent if the caller is already a member.
switch(accessToken: string, organizationId: string): Promise<AuthResultDto>Make `organizationId` the active org for this session (member-only). Returns a fresh {accessToken, refreshToken} pair carrying the active org, **store both**. Subsequent entitlement reads (`billing.getEntitlements`) then default to this org's view + shared pool without passing `organizationId` explicitly. The active org survives token refresh until you switch again, clear it, or leave the org.
clearActive(accessToken: string): Promise<AuthResultDto>Clear the active org, switch the session back to the personal pool. Returns a fresh token pair (no active org); **store both**.
rekey.usageUsageClientrecord(input: { meterSlug: string; quantity: number; /** Attribute to an end-user, or an `organizationId` (shared org pool), or * neither (app-level usage). Pass at most one subject. */ endUserId?: string; organizationId?: string; occurredAt?: string; metadata?: Record<string, unknown>; idempotencyKey?: string; }): Promise<UsageRecordDto>Record a usage event against a named meter. `quantity` is a positive integer. `occurredAt` defaults to server time and must fall in the current UTC month.
aggregate(input: { meterSlug: string; from?: string; to?: string; endUserId?: string; organizationId?: string; }): Promise<UsageAggregateDto>Sum recorded quantity for a meter, optionally bounded by a time window and/or scoped to a subject (`endUserId` or `organizationId`). Drives "you've used X of your Y quota" displays.
getRemaining(accessToken: string, opts?: { meter?: string; organizationId?: string }): Promise<UsageRemainingDto>The signed-in end-user's included quota, usage and remaining units this period, per meter (or one meter with `{ meter }`). Computed by the code `record` enforces with: a record of more than `remaining` is the one that is refused (402 `USAGE_QUOTA_EXCEEDED`) or, on a priced meter, charged.
getRemainingFor(subject: { endUserId: string; organizationId?: string } | { organizationId: string; endUserId?: string }, opts?: { meter?: string }): Promise<UsageRemainingDto>The same answer as `getRemaining`, for a subject you name instead of one whose token you hold: `{ endUserId }`, `{ organizationId }`, or both to read the organization as that member. Secret key only.
listMeters(opts?: { limit?: number; offset?: number }): Promise<Paged<UsageMeterCatalogueEntryDto>>The Application's usage meters: the slugs `record` takes, their units, whether each accepts records, and its fallback credit price. Secret key.
rekey.usersUsersClientServer-side end-user lookup (secret key only). `/users/me` answers "who is this token"; these answer "who is this id / email" for a backend that holds no token.
getByEmail(email: string): Promise<EndUserDto>Exact, case-insensitive email match in the calling Application. Throws END_USER_NOT_FOUND.
get(endUserId: string): Promise<EndUserDto>By id, scoped to the calling Application. Throws END_USER_NOT_FOUND.
getSupport(endUserId: string, options: { deviceId?: string } = {}): Promise<SupportModeDto>Whether an operator has this end-user in support mode, and which capabilities the open support case turns on. Pass `deviceId` to ask about one device: a case limited to devices is active only for a device it names, so without `deviceId` only a case covering every device is active. Rekey reports the state; your backend decides what each capability means.
import(users: ImportUserInput[]): Promise<ImportUsersResult>Import up to 500 users from another auth system in one call. Password hashes (argon2id or bcrypt) are stored as given and verified as-is at sign-in; bcrypt is upgraded to argon2id on first success. Existing addresses are skipped, never updated.
updateProfile(endUserId: string, patch: Record<string, ProfileValue | null>): Promise<ProfileStateDto>Set profile answers for an end user. Any field may be set from the server, including `writableBy: "server"` ones. `null` clears an answer; keys you omit are kept. Throws PROFILE_FIELD_UNKNOWN, PROFILE_FIELD_INVALID or PROFILE_TOO_LARGE. See docs/profile-fields.md.
getEmailPreferences(endUserId: string): Promise<EmailPreferences>Which notification email topics an end user receives. The one-click unsubscribe link writes the same records, so a settings page built on this matches the email. Needs `auth:read`.
updateEmailPreferences(endUserId: string, topics: Record<string, EmailTopicState>): Promise<EmailPreferences>Set notification topics to `subscribed` or `unsubscribed`. Lifting a topic never lifts "unsubscribe from all" or an operator's suppression. Needs the elevated `email:send` scope. Throws EMAIL_TOPIC_UNKNOWN for a topic no notification template was published with.
completeOnboarding(endUserId: string): Promise<ProfileStateDto>Mark an end user's onboarding complete. Every `requiredForOnboarding` field must be answered, or this throws PROFILE_INCOMPLETE with `details.missing`. Completing after a skip is allowed. Idempotent: the first call emits `user.onboarding_completed`, later calls return the same state. See docs/profile-fields.md.
skipOnboarding(endUserId: string): Promise<ProfileStateDto>Record that an end user skipped onboarding. Nothing is validated and nothing is gated on it: Rekey only records the skip, and your app decides where a skipped user goes. Idempotent: the first call emits `user.onboarding_skipped`; a repeat, or a skip after completion, changes nothing and returns the current state. See docs/profile-fields.md.
RekeyOperatorOperator access to one Application's billing: prices, coupons, payments and their refunds, the billing log, reporting and the routing preview. Issuing a refund, changing provider credentials and changing the billing settings need an operator session, so they are in the panel and not here.
new RekeyOperator(config: RekeyOperatorConfig)forApplication(applicationId: string): RekeyOperatorThe same client for another Application of the workspace.
emailVariableIssuesThe per-variable problems of an `EMAIL_VARIABLES_INVALID` error, or an empty array for any other error.
emailVariableIssues(err: unknown): Array<{ path: string; message: string }>isEmailSendErrorTrue when `err` is a `RekeyError` from `rekey.email.send()` with one of its own codes.
isEmailSendError(err: unknown): err is RekeyError & { code: EmailSendErrorCode }normalizeClientIp`value` as a single IP address, or null. Deliberately strict: a list (`a, b`), a port, or anything with whitespace inside is refused rather than guessed at, so what reaches the API is always exactly one address.
normalizeClientIp(value: string | null | undefined): string | nullnormalizeCountryHint`value` as an upper-case ISO 3166-1 alpha-2 country, or null. Cloudflare's `XX` (unknown) and `T1` (Tor) are not countries.
normalizeCountryHint(value: string | null | undefined): string | nullverifyAccessTokenVerify an end-user ACCESS token **offline**, no round-trip to the Rekey API. Works only for Applications that opted into RS256 tokens (`authConfig.tokenAlg = "RS256"`, Panel → Application → Auth); the default HS256 tokens are symmetric and can only be verified by the API itself (use `rekey.auth.getCurrentUser(token)` for those).
verifyAccessToken(token: string, options: VerifyAccessTokenOptions): Promise<VerifiedAccessTokenClaims>verifyWebhookSignatureVerify the HMAC signature on an inbound webhook from Rekey. Returns `true` only when (a) the timestamp is fresh (within `toleranceSeconds`, default 300) AND (b) the signature matches a constant-time compare.
verifyWebhookSignature(args: { header: string | null | undefined; payload: string | Buffer; secret: string; toleranceSeconds?: number; now?: () => number; }): booleanvisitorCountryHintFromThe visitor's country as the hosting platform reports it on an incoming request: Cloudflare's `cf-ipcountry`, else Vercel's `x-vercel-ip-country`. Undefined when neither names a country. Pass the result as {@link RekeyConfig.visitorCountryHint}.
visitorCountryHintFrom(headers: { get(name: string): string | null | undefined; }): string | undefined@rekey.dev/react
pnpm add @rekey.dev/reactBrowser SDK + React hooks/components. Holds only the user access token, never the secret key.
Components
Hooks
RekeyBrowserClientnew RekeyBrowserClient(config: RekeyBrowserConfig)getCurrentUser(accessToken: string, meEndpoint = '/api/v1/auth/me'): Promise<EndUserDto | null>Fetch the current end-user given an access token. Returns null on USER_TOKEN_INVALID, END_USER_BANNED or END_USER_ERASED so callers can render signed-out state without try/catch noise.
getMe(accessToken: string, options: { include?: L; meEndpoint?: string } = {}): Promise<(CurrentUserDto & MeIncludedFor<L>) | null>`getCurrentUser` plus the extras `include` names, from the same user-token-only `GET /api/v1/auth/me`: `entitlements`, `device` (the device this session is bound to, or null), `subscription`, `organization` (the active organization with the caller's role) and `licenses` (`{ items, truncated }`, the first 100 of `listMyLicenses`). With a literal list (inline or `as const`) the return type gains exactly those properties; with a list typed `MeInclude[]` they are optional. Null on the same codes as `getCurrentUser`.
signUp(input: SignUpRequest): Promise<AuthResultDto>Create a new end-user (email + password). Returns the user + session tokens.
signIn(input: SignInRequest): Promise<SignInOutcomeDto>Authenticate (email + password). Returns a `SignInOutcome`, branch on `mfaRequired` before reading `accessToken`; MFA-enrolled users get an `mfaChallengeToken`, complete via `mfaVerify(...)`.
mfaVerify(input: MfaVerifyRequest): Promise<AuthResultDto>Exchange an MFA challenge token + code for a real session.
requestMagicLink(input: { email: string; signInUrl?: string }): Promise<{ delivered: boolean; emailSent: boolean; magicLinkToken: string | null; }>Request a magic-link sign-in email. Enumeration-safe.
verifyMagicLink(input: { token: string }): Promise<SignInOutcomeDto>Consume a magic-link token. Returns a `SignInOutcome` (branch on `mfaRequired`).
refresh(refreshToken: string): Promise<AuthResultDto>Exchange a refresh token for a fresh access/refresh pair.
signOut(refreshToken: string): Promise<{ signedOut: true }>Revoke a refresh token (sign out). Idempotent.
startPasskeyAuthentication(input?: { email?: string }): Promise<{ options: unknown; expectedChallenge: string; }>Begin a passkey authentication ceremony, forward `options` to `navigator.credentials.get`.
verifyPasskeyAuthentication(input: { response: unknown; expectedChallenge: string; }): Promise<SignInOutcomeDto>Complete a passkey authentication. Returns a `SignInOutcome`.
getPlans(page?: ListPage, opts?: { accessToken?: string }): Promise<Paged<PublicPlanDto>>List the Application's active plans (public catalogue, for pricing pages).
validateCoupon(accessToken: string, input: ValidateCouponRequest): Promise<ValidateCouponResultDto>Preview a coupon on a plan for the signed-in buyer, without applying it. The quote is made at the price their checkout would charge, and `formatted.discount` and `formatted.total` are written in that currency: render them, or hand the result to `<CouponQuote>`.
getList(key: string): Promise<ContactListPublicDto>A list's form: its fields and the consent text and version to show. The list needs Public capture on, and the Application a browser origin.
subscribeToList(key: string, input: ListSubscribeRequest): Promise<{ status: 'received' }>Add the visitor to a list. Always resolves `{ status: 'received' }` when the API accepted the request, whatever happened to the address, so a page can never learn who is on a list.
listBillingProviders(opts?: { country?: string; planSlug?: string }): Promise<ProvidersListDto>The billing providers enabled for this Application, in the order the geo router prefers them (the first is the default pick). Powers a "Pay with…" picker at checkout, feed the result straight into `<ProviderPicker>`.
listOAuthProviders(): Promise<OAuthProvidersListDto>The OAuth providers this Application offers for sign-in, one entry per button to render, e.g. `{ providers: [{ id: 'google', name: 'Google' }] }`. Lists only providers the operator configured with both a client id and a secret, and returns names only. Needs only the publishable key.
verifyLicense(input: { key: string; machineFingerprint: string; label?: string; }): Promise<LicenseVerifyResultDto>Verify a license key for this machine. The license `key` is the entitlement bearer; the publishable key only identifies the Application. `ok=false` is a normal result for an invalid/expired license, not an exception.
getSubscription(accessToken: string, opts?: { organizationId?: string; includeEnded?: boolean }): Promise<SelfSubscriptionDto | null>The current subscription, the user's own by default, or an organization's when `opts.organizationId` is passed (org-billed apps; caller must be a member). Returns null when there's no active/pending/past-due subscription.
listSubscriptions(accessToken: string, opts?: { organizationId?: string }): Promise<{ items: SelfSubscriptionDto[] }>Every live (ACTIVE, TRIALING, PAST_DUE) subscription of the user, or of `opts.organizationId` (member-only), the one {@link getSubscription} returns first. Check it before offering a checkout, so a buyer is never offered a plan they already hold.
listOrganizations(accessToken: string, page?: ListPage): Promise<Paged<OrganizationWithRoleDto>>Organizations the signed-in user belongs to, each with their role.
getEntitlements(accessToken: string, opts?: { organizationId?: string }): Promise<EntitlementsDto>The signed-in user's entitlements (features, limits, credit balance).
getUsageRemaining(accessToken: string, opts?: { meter?: string; organizationId?: string }): Promise<UsageRemainingDto>The signed-in user's included quota, usage and remaining units this period, per meter (or one meter with `{ meter }`). `remaining` is what the next record is measured against, so "3 of 100 left" is what the server will enforce. The active organization's quota in an Application that bills organizations; `{ organizationId }` (member-only) picks one.
listMyCreditLedger(accessToken: string, opts?: ListPage & { organizationId?: string }): Promise<Paged<SelfCreditLedgerEntryDto>>The signed-in user's own credit ledger, newest first (their active organization's pool in an Application that bills organizations). Entries carry no `metadata`; that stays with your backend.
getFeature(accessToken: string, key: string, opts?: { organizationId?: string }): Promise<FeatureCheckDto>One feature for the signed-in user: `{ key, granted, value }`. `value` is what `features[key]` holds in `getEntitlements` (null when nothing grants it); `granted` is `Boolean(value)`. Same subject as `getMe(token, { include: ['entitlements'] })`, or the organization you pass.
hasFeature(accessToken: string, key: string, opts?: { organizationId?: string }): Promise<boolean>Whether the signed-in user holds a feature: a false flag, a 0 limit and an unknown key are all `false`. Use `getFeature` to read a numeric limit.
listMyLicenses(accessToken: string, page?: ListPage, opts?: { organizationId?: string }): Promise<Paged<EndUserLicenseDto>>The signed-in user's own licences, newest first, plus the active organization's in an org-billed Application. No raw keys, only each licence's display `keyPrefix`. Pass `{ organizationId }` to include that organization's pool instead; the caller must be a member, or the API answers 403 `ORGANIZATION_NOT_MEMBER`.
listPayments(accessToken: string, limit?: number, offset?: number): Promise<Paged<PortalPaymentDto>>The signed-in user's own payment history, newest first.
cancelSubscription(accessToken: string, opts?: { atPeriodEnd?: boolean; organizationId?: string; subscriptionId?: string; reason?: CancelReason; feedback?: string; }): Promise<SelfSubscriptionDto>Cancel the current subscription (default: at period end). Pass `opts.organizationId` to cancel an org's subscription (caller must be OWNER/ADMIN of that org), and `opts.subscriptionId` to cancel one of several live subscriptions ({@link listSubscriptions}). `reason` and `feedback` are kept with the cancellation and carried on its webhooks.
createCheckout(accessToken: string, input: CreateCheckoutRequest & { couponCode?: string }): Promise<CheckoutResultDto>Start a hosted-checkout session for the signed-in user. Returns the redirect URL.
subscribe(accessToken: string, opts?: { organizationId?: string }): Promise<{ subscription: SelfSubscriptionDto | null; activated: boolean }>Put the signed-in user on the Application's free tier (`billingConfig.defaultPlanSlug`). No payment provider is involved, so this works on an Application with no billing credentials configured at all.
getTrialEligibility(accessToken: string, opts?: { organizationId?: string; planSlug?: string; limit?: number; offset?: number }): Promise<TrialEligibilityDto>Whether the signed-in user may start each plan's free trial.
listMyDevices(accessToken: string, opts?: { status?: DeviceStatusType; limit?: number; offset?: number }): Promise<Paged<EndUserDeviceDto>>The signed-in user's own devices, newest activity first.
releaseMyDevice(accessToken: string, deviceId: string): Promise<{ device: EndUserDeviceDto; sessionsRevoked: number }>Release one of the signed-in user's own devices.
completeOnboarding(accessToken: string): Promise<ProfileStateDto>Mark the signed-in user's onboarding complete. Every `requiredForOnboarding` field must be answered, or this rejects with PROFILE_INCOMPLETE and `details.missing`. Allowed after a skip. Idempotent.
skipOnboarding(accessToken: string): Promise<ProfileStateDto>Record that the signed-in user skipped onboarding. Rekey only records it and gates nothing on it: where a skipped user goes is your app's call. Idempotent, and a no-op once onboarding was completed.
deleteAccount(accessToken: string, proof: { password?: string; code?: string }): Promise<{ erased: true; erasedAt: string }>Erase the signed-in user's own account. It cannot be undone. From the browser the user confirms with their `password`, or `code` when MFA is on. Throws STEP_UP_REQUIRED (401) without a valid one, ACCOUNT_HAS_ACTIVE_SUBSCRIPTION (409) while a subscription still bills, and ACCOUNT_SELF_DELETION_DISABLED (403) when the Application keeps deletion on its server (`authConfig.accountDeletion` `server_only` or `off`).
CheckoutButtonA single checkout button for one plan. Posts the plan slug to your checkout Server Action, which redirects to the hosted checkout. Append the active org id via `hiddenFields` for org-scoped billing.
CheckoutButton(props: CheckoutButtonProps): React.JSX.ElementContactFormA contact form that renders the list's own fields (from its field schema) below the email address.
ContactForm(props: ListFormProps): React.JSX.ElementCouponQuoteThe result of a coupon preview: what the code takes off and what the buyer pays, in the currency their checkout charges. When the quote is for the plan's default price because the price selected could not be charged, it says that first.
CouponQuote({ quote, appearance, className }: CouponQuoteProps): React.JSX.ElementCreateOrganizationStandalone "create a team" card. Delegates to your create Server Action.
CreateOrganization(props: CreateOrganizationProps): React.JSX.ElementLoadingLoading({ children }: { children: React.ReactNode }): React.JSX.Element | nullmcpConnectionInfomcpConnectionInfo(args: { apiUrl: string; appSlug: string }): McpConnectionInfoNewsletterFormA newsletter or waitlist signup: email, the list's consent checkbox, and a hidden honeypot.
NewsletterForm(props: ListFormProps): React.JSX.ElementOrganizationProfileManage a team's members + invitations. Reads members/invitations as props (server-resolved) and delegates every mutation to your Server Actions. Manage affordances render only for OWNER/ADMIN viewers.
OrganizationProfile(props: OrganizationProfileProps): React.JSX.ElementOrganizationSwitcherPick / switch / create the active organization. Reads the org list as a prop (server-resolved) and delegates switching + creation to your Server Actions. Respects `billingSubject='org'` by nudging the user to select a team when none is active.
OrganizationSwitcher(props: OrganizationSwitcherProps): React.JSX.ElementPriceFallbackNoticeTells the buyer, before they pay, that the price in their own currency cannot be charged and what they will pay instead. Renders nothing when there was no fallback, so it can sit in the page unconditionally.
PriceFallbackNotice({ fallback, children, appearance, className, }: PriceFallbackNoticeProps): React.JSX.Element | nullPricingTableRender the Application's plans as a pricing grid with upgrade buttons. Plans come in as a prop (server-fetched); each upgrade posts to your checkout Server Action. Org-scoped when `hiddenFields` carries the active org id, and gated when `orgGateBlocking`.
PricingTable(props: PricingTableProps): React.JSX.ElementProtectGate UI by entitlement / feature / role. Renders `children` only when the user is signed in AND every supplied check passes; otherwise renders `fallback` (or nothing).
Protect({ authorization, feature, role, condition, children, fallback = null, }: ProtectProps): React.JSX.Element | nullProviderPickerA themed "Pay with…" radio-card group, one option per enabled billing provider. The selected value posts as `provider` in the surrounding form's FormData (uncontrolled, zero-JS) or drives a controlled `value`/`onChange` pair. Friendly names (Stripe / PayPal / Razorpay), a labelled radiogroup, and keyboard navigation come built in.
ProviderPicker(props: ProviderPickerProps): React.JSX.ElementRekeyLoadedRenders children once the provider has resolved the session. Pair with `<RekeyLoading>`.
RekeyLoaded({ children }: { children: React.ReactNode }): React.JSX.Element | nullRekeyLoadingRenders children only while the provider is resolving the session. Pair with `<RekeyLoaded>`.
RekeyLoading({ children }: { children: React.ReactNode }): React.JSX.Element | nullRekeyProviderRekeyProvider({ children, apiUrl, publishableKey, initialUser = null, accessToken = null, meEndpoint, }: RekeyProviderProps): React.JSX.ElementRekeyStylesRender the component stylesheet once, yourself.
RekeyStyles({ children }: { children?: React.ReactNode }): React.JSX.ElementSignedInSignedIn({ children }: { children: React.ReactNode }): React.JSX.Element | nullSignedOutSignedOut({ children }: { children: React.ReactNode }): React.JSX.Element | nullSignInDrop-in sign-in card: email + password, optional magic-link, optional OAuth. Delegates the actual sign-in to your server (`action` / `actionUrl`).
SignIn(props: SignInProps): React.JSX.ElementSignInButtonA button that navigates to your sign-in page.
SignInButton(props: NavButtonProps): React.JSX.ElementSignOutButtonA button (form) that invokes your sign-out Server Action.
SignOutButton(props: NavButtonProps): React.JSX.ElementSignUpDrop-in sign-up card. Like `<SignIn>` but posts to your sign-up action.
SignUp(props: SignUpProps): React.JSX.ElementSignUpButtonA button that navigates to your sign-up page.
SignUpButton(props: NavButtonProps): React.JSX.ElementThemedEstablishes the themed scope. Every public component wraps its tree in this: it injects the stylesheet, sets `.rekey-root` + the theme attribute, and applies variable overrides inline. `className` is forwarded to the root so an integrator can target the whole widget; `appearance.elements.root` is merged in too.
Themed({ appearance, className, style, children, }: { // `| undefined` is explicit so callers can spread possibly-undefined props // under `exactOptionalPropertyTypes`. appearance?: AppearanceProp | undefined; className?: string | undefined; style?: React.CSSProperties | undefined; children: React.ReactNode; }): React.JSX.ElementuseAppearanceRead the active appearance, used by `cx` to merge element overrides.
useAppearance(): AppearanceuseListSubscribeuseListSubscribe(key: string, initial?: ContactListPublicDto): UseListSubscribeuseOAuthProvidersThe OAuth providers the Application offers, fetched with the publishable key from `<RekeyProvider>`. `null` until the list arrives, and stays `null` on the server, outside a provider, or when the request fails, so a sign-in page never breaks because of it.
useOAuthProviders(options?: { enabled?: boolean }): OAuthProviderSummaryDto[] | nullUserButtonAvatar + dropdown menu for the signed-in user. Renders nothing when signed out. The avatar shows the user's email initial.
UserButton(props: UserButtonProps): React.JSX.Element | nulluseRekeyManual session refresh. Usually called after a sign-in / sign-out round-trip the customer's server handles, so the provider re-fetches the latest user state.
useRekey(): { refresh: () => Promise<void>; }useUserThe current end-user, or `null` if signed out. Check `user` for null first, then TypeScript narrows the type.
useUser(): { user: EndUserDto | null; signedIn: boolean; loading: boolean; }@rekey.dev/nextjs
pnpm add @rekey.dev/nextjsNext.js App Router adapters: middleware and server helpers built on @rekey.dev/node.
authResolve the current session from cookies. Tries the access token first; on `USER_TOKEN_INVALID` refreshes once. Returns null when signed out.
auth(options?: SessionDeviceOptions): Promise<Session | null>classifySignInErrorClassify a thrown sign-in failure.
classifySignInError(err: unknown): SignInFailure | nullcreateSessionFinalize a **browser** login into httpOnly session cookies.
createSession(tokens: { accessToken: string; refreshToken: string; }): Promise<void>mcpConnectionInfoBuild the MCP connection URL + `claude mcp add` command for an MCP-enabled Application (Panel → Application → MCP). Render a "Connect to Claude" button with this. Pure string-building, the MCP client runs the OAuth flow itself.
mcpConnectionInfo(args: { apiUrl: string; appSlug: string }): McpConnectionInfomfaVerifyServer action: complete an MFA-required sign-in. Sets cookies on success. Throws `RekeyError` with code `MFA_CODE_INVALID` / `MFA_CODE_REUSED` / `MFA_CHALLENGE_INVALID` / `MFA_CHALLENGE_USED` on failure, surface the error message to the user and prompt to retry. The two challenge codes mean the token cannot be retried: send the user back to sign in.
mfaVerify(input: { mfaChallengeToken: string; code: string; device?: DeviceBindingRequest; }, options: VisitorOptions = {}): Promise<SignedInSession>refreshSessionRotate the session and persist it. For a route handler or middleware, where cookie writes are allowed.
refreshSession(options?: SessionDeviceOptions): Promise<Session | null>rejectMalformedActionOriginReturns a 403 for a Server Action whose `Origin` is present but not a URL, or `null` for every other request, which should proceed unchanged.
rejectMalformedActionOrigin(req: ActionRequestLike): NextResponse | nullrekeyMiddlewarerekeyMiddleware(config: MiddlewareConfig = {})rekeyRefreshHandlerThe route `rekeyMiddleware` sends a stale session to. The whole of `app/api/rekey/refresh/route.ts`:
rekeyRefreshHandler(options: RefreshHandlerOptions = {}): (req: Request) => Promise<Response>signInServer action: sign in with email + password.
signIn(input: { email: string; password: string; device?: DeviceBindingRequest; }, options: VisitorOptions = {}): Promise<SignInOutcome>signOutServer action: revoke the refresh token + clear cookies. Optionally pass `redirectTo` to bounce afterwards.
signOut(redirectTo?: string): Promise<void>signUpServer action: sign up + create the user + start a session.
signUp(input: { email: string; password: string; metadata?: Record<string, unknown>; device?: DeviceBindingRequest; }, options: VisitorOptions = {}): Promise<SignedInSession>@rekey.dev/astro
pnpm add @rekey.dev/astroAstro (4 to 7) session handling: middleware that puts the session on Astro.locals, cookie helpers, and sign-out, built on @rekey.dev/node. Sign in with rekey().auth and hand the tokens to setSession. Needs an SSR adapter and output: "server".
RekeyAstroConfigErrorThrown when the package is misconfigured, as opposed to the API failing.
new RekeyAstroConfigError(message: string)clearSessionClear both session cookies.
clearSession(cookies: CookieJar): voidgetSessionResolve the session.
getSession(cookies: CookieJar, request: Request, config: RekeyAstroConfig = {}, options: GetSessionOptions = {}): Promise<Session | null>rekeyThe server client, built on first use.
rekey(config: RekeyAstroConfig = {}): RekeyrekeyMiddlewareAstro middleware that puts the session on `Astro.locals.session`.
rekeyMiddleware(config: RekeyAstroConfig = {})safePathReduce a caller-supplied `next` value to a path on this site.
safePath(next: string | null | undefined, fallback: string): stringsetSessionWrite both session cookies.
setSession(cookies: CookieJar, request: Request, tokens: { accessToken: string; refreshToken: string }, config: RekeyAstroConfig = {}): voidsignOutRevoke the refresh token, then clear the cookies.
signOut(cookies: CookieJar, config: RekeyAstroConfig = {}): Promise<SignOutResult>visitorClientThe server client for a call made on the visitor's behalf (sign-in, sign-up, MFA): it forwards the visitor's address as `X-Rekey-Client-Ip`, so the API's per-IP sign-in limits count the visitor rather than this server, and their User-Agent as `X-Rekey-Client-User-Agent`, so the session records their browser and platform rather than this server's runtime.
visitorClient(context: { request: Request; clientAddress?: string | undefined }, config: RekeyAstroConfig = {}): RekeyvisitorCountryHintFromThe visitor's country as the hosting platform reports it on a request: Cloudflare's `cf-ipcountry`, else Vercel's `x-vercel-ip-country`. Undefined when neither is a two-letter country; Cloudflare sends `XX` for unknown and `T1` for Tor.
visitorCountryHintFrom(h: Headers): string | undefinedWas this page helpful?
