# Sign up anonymously Source: https://docs.hexclave.com/api/client/anonymous/sign-up-anonymously /openapi/client.json post /auth/anonymous/sign-up Create a new anonymous account with no email # Create team API key Source: https://docs.hexclave.com/api/client/api-keys/create-team-api-key /openapi/client.json post /team-api-keys Create a new API key for a user or team # Create user API key Source: https://docs.hexclave.com/api/client/api-keys/create-user-api-key /openapi/client.json post /user-api-keys Create a new API key for a user or team # Get team API key details Source: https://docs.hexclave.com/api/client/api-keys/get-team-api-key-details /openapi/client.json get /team-api-keys/{api_key_id} Get details of a specific team API key # Get user API key details Source: https://docs.hexclave.com/api/client/api-keys/get-user-api-key-details /openapi/client.json get /user-api-keys/{api_key_id} Get details of a specific user API key # List team API keys Source: https://docs.hexclave.com/api/client/api-keys/list-team-api-keys /openapi/client.json get /team-api-keys List all team API keys for the project with their metadata and status # List user API keys Source: https://docs.hexclave.com/api/client/api-keys/list-user-api-keys /openapi/client.json get /user-api-keys List all user API keys for the project with their metadata and status # Update team API key Source: https://docs.hexclave.com/api/client/api-keys/update-team-api-key /openapi/client.json patch /team-api-keys/{api_key_id} Update an team API key # Update user API key Source: https://docs.hexclave.com/api/client/api-keys/update-user-api-key /openapi/client.json patch /user-api-keys/{api_key_id} Update an user API key # /api/v1 Source: https://docs.hexclave.com/api/client/apiv1 /openapi/client.json get / Returns a human-readable message with some useful information about the API. # Complete CLI authentication Source: https://docs.hexclave.com/api/client/cli-authentication/complete-cli-authentication /openapi/client.json post /auth/cli/complete Inspect, claim, or complete a CLI authentication session # Initiate CLI authentication Source: https://docs.hexclave.com/api/client/cli-authentication/initiate-cli-authentication /openapi/client.json post /auth/cli Create a new CLI authentication session and return polling and login codes # Poll CLI authentication status Source: https://docs.hexclave.com/api/client/cli-authentication/poll-cli-authentication-status /openapi/client.json post /auth/cli/poll Check the status of a CLI authentication session using the polling code # List connected accounts Source: https://docs.hexclave.com/api/client/connected-accounts/list-connected-accounts /openapi/client.json get /connected-accounts/{user_id} Retrieves a list of all connected accounts for a user. # Check email verification code Source: https://docs.hexclave.com/api/client/contact-channels/check-email-verification-code /openapi/client.json post /contact-channels/verify/check-code Check if an email verification code is valid without using it # Create a contact channel Source: https://docs.hexclave.com/api/client/contact-channels/create-a-contact-channel /openapi/client.json post /contact-channels Add a new contact channel for a user. # Delete a contact channel Source: https://docs.hexclave.com/api/client/contact-channels/delete-a-contact-channel /openapi/client.json delete /contact-channels/{user_id}/{contact_channel_id} Removes a contact channel for a given user. # Get a contact channel Source: https://docs.hexclave.com/api/client/contact-channels/get-a-contact-channel /openapi/client.json get /contact-channels/{user_id}/{contact_channel_id} Retrieves a specific contact channel by the user ID and the contact channel ID. # List contact channels Source: https://docs.hexclave.com/api/client/contact-channels/list-contact-channels /openapi/client.json get /contact-channels Retrieves a list of all contact channels for a user. # Send contact channel verification code Source: https://docs.hexclave.com/api/client/contact-channels/send-contact-channel-verification-code /openapi/client.json post /contact-channels/{user_id}/{contact_channel_id}/send-verification-code Send a code to the user's contact channel for verifying the contact channel. # Update a contact channel Source: https://docs.hexclave.com/api/client/contact-channels/update-a-contact-channel /openapi/client.json patch /contact-channels/{user_id}/{contact_channel_id} Updates an existing contact channel. Only the values provided will be updated. # Verify an email Source: https://docs.hexclave.com/api/client/contact-channels/verify-an-email /openapi/client.json post /contact-channels/verify Verify an email address of a user # List notification preferences Source: https://docs.hexclave.com/api/client/emails/list-notification-preferences /openapi/client.json get /emails/notification-preference/{user_id} Get all notification preferences for a user, showing which notification categories are enabled or disabled. # Update notification preference Source: https://docs.hexclave.com/api/client/emails/update-notification-preference /openapi/client.json patch /emails/notification-preference/{user_id}/{notification_category_id} Enable or disable a specific notification category for a user. # Create cross-domain auth handoff redirect Source: https://docs.hexclave.com/api/client/oauth/create-cross-domain-auth-handoff-redirect /openapi/client.json post /auth/oauth/cross-domain/authorize Creates a one-time OAuth authorization code redirect for cross-domain sign-in handoff using PKCE. # Delete an OAuth provider Source: https://docs.hexclave.com/api/client/oauth/delete-an-oauth-provider /openapi/client.json delete /oauth-providers/{user_id}/{provider_id} Removes an OAuth provider for a given user. # Get an OAuth provider Source: https://docs.hexclave.com/api/client/oauth/get-an-oauth-provider /openapi/client.json get /oauth-providers/{user_id}/{provider_id} Retrieves a specific OAuth provider by the user ID and the OAuth provider ID. # List OAuth providers Source: https://docs.hexclave.com/api/client/oauth/list-oauth-providers /openapi/client.json get /oauth-providers Retrieves a list of all OAuth providers for a user. # OAuth authorize endpoint Source: https://docs.hexclave.com/api/client/oauth/oauth-authorize-endpoint /openapi/client.json get /auth/oauth/authorize/{provider_id} This endpoint is used to initiate the OAuth authorization flow. there are two purposes for this endpoint: 1. Authenticate a user with an OAuth provider. 2. Link an existing user with an OAuth provider. # OAuth token endpoints Source: https://docs.hexclave.com/api/client/oauth/oauth-token-endpoints /openapi/client.json post /auth/oauth/token This endpoint is used to exchange an authorization code or refresh token for an access token. # Update an OAuth provider Source: https://docs.hexclave.com/api/client/oauth/update-an-oauth-provider /openapi/client.json patch /oauth-providers/{user_id}/{provider_id} Updates an existing OAuth provider for a user. # Check sign in code Source: https://docs.hexclave.com/api/client/otp/check-sign-in-code /openapi/client.json post /auth/otp/sign-in/check-code Check if a sign in code is valid without using it # MFA sign in Source: https://docs.hexclave.com/api/client/otp/mfa-sign-in /openapi/client.json post /auth/mfa/sign-in Complete multi-factor authorization to sign in, with a TOTP and an MFA attempt code # Send sign-in code Source: https://docs.hexclave.com/api/client/otp/send-sign-in-code /openapi/client.json post /auth/otp/send-sign-in-code Send a code to the user's email address for sign-in. # Sign in with a code Source: https://docs.hexclave.com/api/client/otp/sign-in-with-a-code /openapi/client.json post /auth/otp/sign-in # Check reset password code Source: https://docs.hexclave.com/api/client/password/check-reset-password-code /openapi/client.json post /auth/password/reset/check-code Check if a reset password code is valid without using it # Reset password with a code Source: https://docs.hexclave.com/api/client/password/reset-password-with-a-code /openapi/client.json post /auth/password/reset Reset password with a code # Send reset password code Source: https://docs.hexclave.com/api/client/password/send-reset-password-code /openapi/client.json post /auth/password/send-reset-code Send a code to the user's email address for resetting the password. # Set password Source: https://docs.hexclave.com/api/client/password/set-password /openapi/client.json post /auth/password/set Set a new password for the current user # Sign in with email and password Source: https://docs.hexclave.com/api/client/password/sign-in-with-email-and-password /openapi/client.json post /auth/password/sign-in Sign in to an account with email and password # Sign up with email and password Source: https://docs.hexclave.com/api/client/password/sign-up-with-email-and-password /openapi/client.json post /auth/password/sign-up Create a new account with email and password # Update password Source: https://docs.hexclave.com/api/client/password/update-password /openapi/client.json post /auth/password/update Update the password of the current user, requires the old password # Create Purchase Session Source: https://docs.hexclave.com/api/client/payments/create-purchase-session /openapi/client.json post /payments/purchases/purchase-session Creates a purchase session for completing a purchase. # Create Purchase URL Source: https://docs.hexclave.com/api/client/payments/create-purchase-url /openapi/client.json post /payments/purchases/create-purchase-url Creates a secure checkout URL for purchasing a product. # Get Item Source: https://docs.hexclave.com/api/client/payments/get-item /openapi/client.json get /payments/items/{customer_type}/{customer_id}/{item_id} Retrieves information about a specific item (credits, quotas, etc.) for a customer. # Validate Purchase Code Source: https://docs.hexclave.com/api/client/payments/validate-purchase-code /openapi/client.json post /payments/purchases/validate-code Validates a purchase verification code and returns purchase details including available prices. # List project permissions Source: https://docs.hexclave.com/api/client/permissions/list-project-permissions /openapi/client.json get /project-permissions List global permissions of the current user. `user_id=me` must be set for client requests. `(user_id, permission_id)` together uniquely identify a permission. # List team permissions Source: https://docs.hexclave.com/api/client/permissions/list-team-permissions /openapi/client.json get /team-permissions List team permissions of the current user. `user_id=me` must be set for client requests. Note that this might contain the permissions with the same permission ID across different teams. `(team_id, user_id, permission_id)` together uniquely identify a permission. # Get the current project Source: https://docs.hexclave.com/api/client/projects/get-the-current-project /openapi/client.json get /projects/current Get the current project information including display name, OAuth providers and authentication methods. Useful for displaying the available login options to the user. # Delete session Source: https://docs.hexclave.com/api/client/sessions/delete-session /openapi/client.json delete /auth/sessions/{id} Delete a session by ID. # List sessions Source: https://docs.hexclave.com/api/client/sessions/list-sessions /openapi/client.json get /auth/sessions List all sessions for the current user. # Refresh access token Source: https://docs.hexclave.com/api/client/sessions/refresh-access-token /openapi/client.json post /auth/sessions/current/refresh Get a new access token using a refresh token # Sign out of the current session Source: https://docs.hexclave.com/api/client/sessions/sign-out-of-the-current-session /openapi/client.json delete /auth/sessions/current Sign out of the current session and invalidate the refresh token # Accept a team invitation by ID Source: https://docs.hexclave.com/api/client/teams/accept-a-team-invitation-by-id /openapi/client.json post /team-invitations/{id}/accept Accepts a team invitation for the specified user. The user must have a verified email matching the invitation's recipient email. This marks the invitation as used and adds the user to the team. # Accept the team invitation Source: https://docs.hexclave.com/api/client/teams/accept-the-team-invitation /openapi/client.json post /team-invitations/accept Accept invitation and add user to the team # Check if a team invitation code is valid Source: https://docs.hexclave.com/api/client/teams/check-if-a-team-invitation-code-is-valid /openapi/client.json post /team-invitations/accept/check-code Check if a team invitation code is valid without using it # Create a team Source: https://docs.hexclave.com/api/client/teams/create-a-team /openapi/client.json post /teams Create a new team and optionally add the current user as a member. # Delete a team Source: https://docs.hexclave.com/api/client/teams/delete-a-team /openapi/client.json delete /teams/{team_id} Delete a team. Only allowed if the current user is a member of the team and has the `$delete_team` permission. # Delete a team invitation Source: https://docs.hexclave.com/api/client/teams/delete-a-team-invitation /openapi/client.json delete /team-invitations/{id} # Get a team Source: https://docs.hexclave.com/api/client/teams/get-a-team /openapi/client.json get /teams/{team_id} Get a team that the current user is a member of. # Get a team member profile Source: https://docs.hexclave.com/api/client/teams/get-a-team-member-profile /openapi/client.json get /team-member-profiles/{team_id}/{user_id} Get a team member profile. you can always get your own profile by setting `me` as the `user_id` in the path parameters on the client. If you want to get someone else's profile in a team, you need to have the `$read_members` permission in that team. # Get team invitation details Source: https://docs.hexclave.com/api/client/teams/get-team-invitation-details /openapi/client.json post /team-invitations/accept/details Get additional information about a team invitation code # List team invitations Source: https://docs.hexclave.com/api/client/teams/list-team-invitations /openapi/client.json get /team-invitations # List team members profiles Source: https://docs.hexclave.com/api/client/teams/list-team-members-profiles /openapi/client.json get /team-member-profiles List team members profiles. You always need to specify a `team_id` that your are a member of on the client. You can always filter for your own profile by setting `me` as the `user_id` in the path parameters. If you want list all the profiles in a team, you need to have the `$read_members` permission in that team. # List teams Source: https://docs.hexclave.com/api/client/teams/list-teams /openapi/client.json get /teams List all the teams that the current user is a member of. `user_id=me` must be passed in the query parameters. # Remove a user from a team Source: https://docs.hexclave.com/api/client/teams/remove-a-user-from-a-team /openapi/client.json delete /team-memberships/{team_id}/{user_id} All the users are allowed to remove themselves from a team (`user_id=me`). Only the users who have the `$remove_members` permission are allowed to remove other users from a team. `team_id` is must an ID of a team that the user is a member of. # Send an email to invite a user to a team Source: https://docs.hexclave.com/api/client/teams/send-an-email-to-invite-a-user-to-a-team /openapi/client.json post /team-invitations/send-code The user receiving this email can join the team by clicking on the link in the email. If the user does not have an account yet, they will be prompted to create one. # Update a team Source: https://docs.hexclave.com/api/client/teams/update-a-team /openapi/client.json patch /teams/{team_id} Update the team information. Only allowed if the current user is a member of the team and has the `$update_team` permission. # Update your team member profile Source: https://docs.hexclave.com/api/client/teams/update-your-team-member-profile /openapi/client.json patch /team-member-profiles/{team_id}/{user_id} Update your own team member profile. `user_id` must be `me` in the path parameters on the client. # Delete current user Source: https://docs.hexclave.com/api/client/users/delete-current-user /openapi/client.json delete /users/me Deletes the currently authenticated user. Use this with caution. # Get current user Source: https://docs.hexclave.com/api/client/users/get-current-user /openapi/client.json get /users/me Gets the currently authenticated user. # Update current user Source: https://docs.hexclave.com/api/client/users/update-current-user /openapi/client.json patch /users/me Updates the currently authenticated user. Only the values provided will be updated. # Overview Source: https://docs.hexclave.com/api/overview Complete REST API documentation for Hexclave Stack offers a REST API for backends & frontends of any programming language or framework. This API is used to authenticate users, manage user data, and more. ## Authentication Hexclave uses different authentication patterns depending on whether you're making requests from client-side code (browser, mobile app) or server-side code (your backend). **Security Critical**: Never expose your secret server key (`ssk_...`) in client-side code, browser requests, or any publicly accessible location. Server keys should only be used in secure backend environments. ### Client-Side Authentication For requests from browsers, mobile apps, or other client-side environments: ```bash theme={null} curl https://api.hexclave.com/api/v1/ \ -H "X-Stack-Access-Type: client" \ -H "X-Stack-Project-Id: " \ -H "X-Stack-Publishable-Client-Key: pck_" \ -H "X-Stack-Access-Token: " ``` ### Server-Side Authentication For requests from your secure backend server: ```bash theme={null} curl https://api.hexclave.com/api/v1/ \ -H "X-Stack-Access-Type: server" \ -H "X-Stack-Project-Id: " \ -H "X-Stack-Secret-Server-Key: ssk_" ``` ### Authentication Headers | Header | Type | Used In | Description | | -------------------------------- | ---------------------- | ----------- | ---------------------------------------------------------------------------------------- | | `X-Stack-Access-Type` | `"client" \| "server"` | Both | Required. Use `"client"` for frontend/browser requests, `"server"` for backend requests. | | `X-Stack-Project-Id` | UUID | Both | Required. Your project ID from the Stack dashboard. | | `X-Stack-Publishable-Client-Key` | string | Client only | Required for client access. Safe to expose in frontend code. Starts with `pck_`. | | `X-Stack-Secret-Server-Key` | string | Server only | Required for server access. **Never expose in client code**. Starts with `ssk_`. | | `X-Stack-Access-Token` | string | Client only | Optional. The current user's access token. Used to act on behalf of a specific user. | To set up a backend in JavaScript, Python, or another language using the REST API, see [Setup](/guides/getting-started/setup). ## Getting Started Select the API category that matches your use case. Configure the appropriate authentication method (sessions, API keys, or webhook verification). Use the documented endpoints with proper authentication headers. Process the API responses according to the documentation and error handling guidelines. ## FAQ Any language that has the ability to send HTTP requests can use the Stack REST API. This includes JavaScript, Python, Ruby, Java, Go, C#, Dart, and many more. **Client access type** (`X-Stack-Access-Type: client`) is for client-side applications like browsers and mobile apps. Client APIs can only read and update the currently authenticated user's data. Use your publishable client key (`pck_...`) - it's safe to include in frontend code. **Server access type** (`X-Stack-Access-Type: server`) is for your secure backend server. It has full access over all user data using your secret server key (`ssk_...`). Never use server access type or secret server keys in client-side code, browser requests, or any publicly accessible location. Always keep server keys secure on your backend. For more information, see the [`HexclaveClientApp` and `HexclaveServerApp` SDK reference](/sdk/objects/hexclave-app). If you'd like to build your own version of the Stack dashboard (or update project configuration programmatically), you can use the `admin` access type. These endpoints are very dangerous and you should only use them if you know what you're doing. For more information, see the [`HexclaveClientApp` and `HexclaveServerApp` SDK reference](/sdk/objects/hexclave-app). Hexclave API returns standard HTTP status codes. Common error responses include: * `400 Bad Request` - Invalid request parameters * `401 Unauthorized` - Invalid or missing authentication * `403 Forbidden` - Insufficient permissions * `404 Not Found` - Resource not found * `429 Too Many Requests` - Rate limit exceeded * `500 Internal Server Error` - Server error Error responses include a JSON body with additional details about the error. Yes, Hexclave implements rate limiting to ensure fair usage and system stability. Rate limits vary by endpoint and access type. When you exceed the rate limit, you'll receive a `429 Too Many Requests` response with headers indicating when you can retry. ## Need Help? Check the Getting Started Guide for initial setup. Visit the Concepts section for Hexclave fundamentals. Join the Discord community for support and discussions. # Sign up anonymously Source: https://docs.hexclave.com/api/server/anonymous/sign-up-anonymously /openapi/server.json post /auth/anonymous/sign-up Create a new anonymous account with no email # Check team API key validity Source: https://docs.hexclave.com/api/server/api-keys/check-team-api-key-validity /openapi/server.json post /team-api-keys/check Validate a team API key # Check user API key validity Source: https://docs.hexclave.com/api/server/api-keys/check-user-api-key-validity /openapi/server.json post /user-api-keys/check Validate a user API key # Create team API key Source: https://docs.hexclave.com/api/server/api-keys/create-team-api-key /openapi/server.json post /team-api-keys Create a new API key for a user or team # Create user API key Source: https://docs.hexclave.com/api/server/api-keys/create-user-api-key /openapi/server.json post /user-api-keys Create a new API key for a user or team # Get team API key details Source: https://docs.hexclave.com/api/server/api-keys/get-team-api-key-details /openapi/server.json get /team-api-keys/{api_key_id} Get details of a specific team API key # Get user API key details Source: https://docs.hexclave.com/api/server/api-keys/get-user-api-key-details /openapi/server.json get /user-api-keys/{api_key_id} Get details of a specific user API key # List team API keys Source: https://docs.hexclave.com/api/server/api-keys/list-team-api-keys /openapi/server.json get /team-api-keys List all team API keys for the project with their metadata and status # List user API keys Source: https://docs.hexclave.com/api/server/api-keys/list-user-api-keys /openapi/server.json get /user-api-keys List all user API keys for the project with their metadata and status # Update team API key Source: https://docs.hexclave.com/api/server/api-keys/update-team-api-key /openapi/server.json patch /team-api-keys/{api_key_id} Update an team API key # Update user API key Source: https://docs.hexclave.com/api/server/api-keys/update-user-api-key /openapi/server.json patch /user-api-keys/{api_key_id} Update an user API key # /api/v1 Source: https://docs.hexclave.com/api/server/apiv1 /openapi/server.json get / Returns a human-readable message with some useful information about the API. # Complete CLI authentication Source: https://docs.hexclave.com/api/server/cli-authentication/complete-cli-authentication /openapi/server.json post /auth/cli/complete Inspect, claim, or complete a CLI authentication session # Initiate CLI authentication Source: https://docs.hexclave.com/api/server/cli-authentication/initiate-cli-authentication /openapi/server.json post /auth/cli Create a new CLI authentication session and return polling and login codes # Poll CLI authentication status Source: https://docs.hexclave.com/api/server/cli-authentication/poll-cli-authentication-status /openapi/server.json post /auth/cli/poll Check the status of a CLI authentication session using the polling code # List connected accounts Source: https://docs.hexclave.com/api/server/connected-accounts/list-connected-accounts /openapi/server.json get /connected-accounts/{user_id} Retrieves a list of all connected accounts for a user. # Check email verification code Source: https://docs.hexclave.com/api/server/contact-channels/check-email-verification-code /openapi/server.json post /contact-channels/verify/check-code Check if an email verification code is valid without using it # Create a contact channel Source: https://docs.hexclave.com/api/server/contact-channels/create-a-contact-channel /openapi/server.json post /contact-channels Add a new contact channel for a user. # Delete a contact channel Source: https://docs.hexclave.com/api/server/contact-channels/delete-a-contact-channel /openapi/server.json delete /contact-channels/{user_id}/{contact_channel_id} Removes a contact channel for a given user. # Get a contact channel Source: https://docs.hexclave.com/api/server/contact-channels/get-a-contact-channel /openapi/server.json get /contact-channels/{user_id}/{contact_channel_id} Retrieves a specific contact channel by the user ID and the contact channel ID. # List contact channels Source: https://docs.hexclave.com/api/server/contact-channels/list-contact-channels /openapi/server.json get /contact-channels Retrieves a list of all contact channels for a user. # Send contact channel verification code Source: https://docs.hexclave.com/api/server/contact-channels/send-contact-channel-verification-code /openapi/server.json post /contact-channels/{user_id}/{contact_channel_id}/send-verification-code Send a code to the user's contact channel for verifying the contact channel. # Update a contact channel Source: https://docs.hexclave.com/api/server/contact-channels/update-a-contact-channel /openapi/server.json patch /contact-channels/{user_id}/{contact_channel_id} Updates an existing contact channel. Only the values provided will be updated. # Verify an email Source: https://docs.hexclave.com/api/server/contact-channels/verify-an-email /openapi/server.json post /contact-channels/verify Verify an email address of a user # Retrieve encrypted value from data vault Source: https://docs.hexclave.com/api/server/datavault/retrieve-encrypted-value-from-data-vault /openapi/server.json post /data-vault/stores/{id}/get Retrieves and decrypts a value from the data vault using a hashed key # Store encrypted value in data vault Source: https://docs.hexclave.com/api/server/datavault/store-encrypted-value-in-data-vault /openapi/server.json post /data-vault/stores/{id}/set Stores a hashed key and encrypted value in the data vault for a specific store # Activate email capacity boost Source: https://docs.hexclave.com/api/server/emails/activate-email-capacity-boost /openapi/server.json post /emails/capacity-boost Temporarily increases email capacity by 4x for 4 hours. # Get email delivery info Source: https://docs.hexclave.com/api/server/emails/get-email-delivery-info /openapi/server.json get /emails/delivery-info Returns delivery statistics and capacity information for the current tenancy. # Get email outbox entry Source: https://docs.hexclave.com/api/server/emails/get-email-outbox-entry /openapi/server.json get /emails/outbox/{id} Gets a single email from the outbox by ID. # List email outbox Source: https://docs.hexclave.com/api/server/emails/list-email-outbox /openapi/server.json get /emails/outbox Lists all emails in the outbox with optional filtering by status, simple_status, or user_id. # List notification preferences Source: https://docs.hexclave.com/api/server/emails/list-notification-preferences /openapi/server.json get /emails/notification-preference/{user_id} Get all notification preferences for a user, showing which notification categories are enabled or disabled. # Send email Source: https://docs.hexclave.com/api/server/emails/send-email /openapi/server.json post /emails/send-email Send an email to a list of users. The content field should contain either {html} for HTML emails, {template_id, variables} for template-based emails, or {draft_id} for a draft email. # Update email outbox entry Source: https://docs.hexclave.com/api/server/emails/update-email-outbox-entry /openapi/server.json patch /emails/outbox/{id} Updates an email in the outbox. Can be used to edit email content, pause/resume, or cancel emails. Only emails in editable states (`paused`, `preparing`, `rendering`, `render-error`, `scheduled`, `queued`, `server-error`) can be modified. # Update notification preference Source: https://docs.hexclave.com/api/server/emails/update-notification-preference /openapi/server.json patch /emails/notification-preference/{user_id}/{notification_category_id} Enable or disable a specific notification category for a user. # Create an OAuth provider Source: https://docs.hexclave.com/api/server/oauth/create-an-oauth-provider /openapi/server.json post /oauth-providers Add a new OAuth provider for a user. # Create cross-domain auth handoff redirect Source: https://docs.hexclave.com/api/server/oauth/create-cross-domain-auth-handoff-redirect /openapi/server.json post /auth/oauth/cross-domain/authorize Creates a one-time OAuth authorization code redirect for cross-domain sign-in handoff using PKCE. # Delete an OAuth provider Source: https://docs.hexclave.com/api/server/oauth/delete-an-oauth-provider /openapi/server.json delete /oauth-providers/{user_id}/{provider_id} Removes an OAuth provider for a given user. # Get an OAuth provider Source: https://docs.hexclave.com/api/server/oauth/get-an-oauth-provider /openapi/server.json get /oauth-providers/{user_id}/{provider_id} Retrieves a specific OAuth provider by the user ID and the OAuth provider ID. # List OAuth providers Source: https://docs.hexclave.com/api/server/oauth/list-oauth-providers /openapi/server.json get /oauth-providers Retrieves a list of all OAuth providers for a user. # OAuth authorize endpoint Source: https://docs.hexclave.com/api/server/oauth/oauth-authorize-endpoint /openapi/server.json get /auth/oauth/authorize/{provider_id} This endpoint is used to initiate the OAuth authorization flow. there are two purposes for this endpoint: 1. Authenticate a user with an OAuth provider. 2. Link an existing user with an OAuth provider. # OAuth token endpoints Source: https://docs.hexclave.com/api/server/oauth/oauth-token-endpoints /openapi/server.json post /auth/oauth/token This endpoint is used to exchange an authorization code or refresh token for an access token. # Update an OAuth provider Source: https://docs.hexclave.com/api/server/oauth/update-an-oauth-provider /openapi/server.json patch /oauth-providers/{user_id}/{provider_id} Updates an existing OAuth provider. Only the values provided will be updated. # Check sign in code Source: https://docs.hexclave.com/api/server/otp/check-sign-in-code /openapi/server.json post /auth/otp/sign-in/check-code Check if a sign in code is valid without using it # MFA sign in Source: https://docs.hexclave.com/api/server/otp/mfa-sign-in /openapi/server.json post /auth/mfa/sign-in Complete multi-factor authorization to sign in, with a TOTP and an MFA attempt code # Send sign-in code Source: https://docs.hexclave.com/api/server/otp/send-sign-in-code /openapi/server.json post /auth/otp/send-sign-in-code Send a code to the user's email address for sign-in. # Sign in with a code Source: https://docs.hexclave.com/api/server/otp/sign-in-with-a-code /openapi/server.json post /auth/otp/sign-in # Check reset password code Source: https://docs.hexclave.com/api/server/password/check-reset-password-code /openapi/server.json post /auth/password/reset/check-code Check if a reset password code is valid without using it # Reset password with a code Source: https://docs.hexclave.com/api/server/password/reset-password-with-a-code /openapi/server.json post /auth/password/reset Reset password with a code # Send reset password code Source: https://docs.hexclave.com/api/server/password/send-reset-password-code /openapi/server.json post /auth/password/send-reset-code Send a code to the user's email address for resetting the password. # Set password Source: https://docs.hexclave.com/api/server/password/set-password /openapi/server.json post /auth/password/set Set a new password for the current user # Sign in with email and password Source: https://docs.hexclave.com/api/server/password/sign-in-with-email-and-password /openapi/server.json post /auth/password/sign-in Sign in to an account with email and password # Sign up with email and password Source: https://docs.hexclave.com/api/server/password/sign-up-with-email-and-password /openapi/server.json post /auth/password/sign-up Create a new account with email and password # Update password Source: https://docs.hexclave.com/api/server/password/update-password /openapi/server.json post /auth/password/update Update the password of the current user, requires the old password # Create Purchase Session Source: https://docs.hexclave.com/api/server/payments/create-purchase-session /openapi/server.json post /payments/purchases/purchase-session Creates a purchase session for completing a purchase. # Create Purchase URL Source: https://docs.hexclave.com/api/server/payments/create-purchase-url /openapi/server.json post /payments/purchases/create-purchase-url Creates a secure checkout URL for purchasing a product. # Get Item Source: https://docs.hexclave.com/api/server/payments/get-item /openapi/server.json get /payments/items/{customer_type}/{customer_id}/{item_id} Retrieves information about a specific item (credits, quotas, etc.) for a customer. # Update Item Quantity Source: https://docs.hexclave.com/api/server/payments/update-item-quantity /openapi/server.json post /payments/items/{customer_type}/{customer_id}/{item_id}/update-quantity Updates the quantity of an item for a customer. Can increase or decrease quantities, with optional expiration and negative balance control. # Validate Purchase Code Source: https://docs.hexclave.com/api/server/payments/validate-purchase-code /openapi/server.json post /payments/purchases/validate-code Validates a purchase verification code and returns purchase details including available prices. # Grant a global permission to a user Source: https://docs.hexclave.com/api/server/permissions/grant-a-global-permission-to-a-user /openapi/server.json post /project-permissions/{user_id}/{permission_id} Grant a global permission to a user (the permission must be created first on the Hexclave dashboard) # Grant a team permission to a user Source: https://docs.hexclave.com/api/server/permissions/grant-a-team-permission-to-a-user /openapi/server.json post /team-permissions/{team_id}/{user_id}/{permission_id} Grant a team permission to a user (the team permission must be created first on the Hexclave dashboard) # List project permissions Source: https://docs.hexclave.com/api/server/permissions/list-project-permissions /openapi/server.json get /project-permissions Query and filter the permission with `user_id` and `permission_id`. `(user_id, permission_id)` together uniquely identify a permission. # List team permissions of a user Source: https://docs.hexclave.com/api/server/permissions/list-team-permissions-of-a-user /openapi/server.json get /team-permissions Query and filter the permission with `team_id`, `user_id`, and `permission_id`. Note that this might contain the permissions with the same permission ID across different teams and users. `(team_id, user_id, permission_id)` together uniquely identify a permission. # Revoke a global permission from a user Source: https://docs.hexclave.com/api/server/permissions/revoke-a-global-permission-from-a-user /openapi/server.json delete /project-permissions/{user_id}/{permission_id} Revoke a global permission from a user # Revoke a team permission from a user Source: https://docs.hexclave.com/api/server/permissions/revoke-a-team-permission-from-a-user /openapi/server.json delete /team-permissions/{team_id}/{user_id}/{permission_id} Revoke a team permission from a user # Get the current project Source: https://docs.hexclave.com/api/server/projects/get-the-current-project /openapi/server.json get /projects/current Get the current project information including display name, OAuth providers and authentication methods. Useful for displaying the available login options to the user. # Create session Source: https://docs.hexclave.com/api/server/sessions/create-session /openapi/server.json post /auth/sessions Create a new session for a given user. This will return a refresh token that can be used to impersonate the user. # Delete session Source: https://docs.hexclave.com/api/server/sessions/delete-session /openapi/server.json delete /auth/sessions/{id} Delete a session by ID. # List sessions Source: https://docs.hexclave.com/api/server/sessions/list-sessions /openapi/server.json get /auth/sessions List all sessions for the current user. # Refresh access token Source: https://docs.hexclave.com/api/server/sessions/refresh-access-token /openapi/server.json post /auth/sessions/current/refresh Get a new access token using a refresh token # Sign out of the current session Source: https://docs.hexclave.com/api/server/sessions/sign-out-of-the-current-session /openapi/server.json delete /auth/sessions/current Sign out of the current session and invalidate the refresh token # Accept a team invitation by ID Source: https://docs.hexclave.com/api/server/teams/accept-a-team-invitation-by-id /openapi/server.json post /team-invitations/{id}/accept Accepts a team invitation for the specified user. The user must have a verified email matching the invitation's recipient email. This marks the invitation as used and adds the user to the team. # Accept the team invitation Source: https://docs.hexclave.com/api/server/teams/accept-the-team-invitation /openapi/server.json post /team-invitations/accept Accept invitation and add user to the team # Add a user to a team Source: https://docs.hexclave.com/api/server/teams/add-a-user-to-a-team /openapi/server.json post /team-memberships/{team_id}/{user_id} # Check if a team invitation code is valid Source: https://docs.hexclave.com/api/server/teams/check-if-a-team-invitation-code-is-valid /openapi/server.json post /team-invitations/accept/check-code Check if a team invitation code is valid without using it # Create a team Source: https://docs.hexclave.com/api/server/teams/create-a-team /openapi/server.json post /teams Create a new team and optionally add the current user as a member. # Delete a team Source: https://docs.hexclave.com/api/server/teams/delete-a-team /openapi/server.json delete /teams/{team_id} Delete a team by ID. # Delete a team invitation Source: https://docs.hexclave.com/api/server/teams/delete-a-team-invitation /openapi/server.json delete /team-invitations/{id} # Get a team Source: https://docs.hexclave.com/api/server/teams/get-a-team /openapi/server.json get /teams/{team_id} Get a team by ID. # Get a team member profile Source: https://docs.hexclave.com/api/server/teams/get-a-team-member-profile /openapi/server.json get /team-member-profiles/{team_id}/{user_id} Get a team member profile by user ID # Get team invitation details Source: https://docs.hexclave.com/api/server/teams/get-team-invitation-details /openapi/server.json post /team-invitations/accept/details Get additional information about a team invitation code # List team invitations Source: https://docs.hexclave.com/api/server/teams/list-team-invitations /openapi/server.json get /team-invitations # List team members profiles Source: https://docs.hexclave.com/api/server/teams/list-team-members-profiles /openapi/server.json get /team-member-profiles List team members profiles and filter by team ID and user ID # List teams Source: https://docs.hexclave.com/api/server/teams/list-teams /openapi/server.json get /teams List all the teams in the project. # Remove a user from a team Source: https://docs.hexclave.com/api/server/teams/remove-a-user-from-a-team /openapi/server.json delete /team-memberships/{team_id}/{user_id} # Send an email to invite a user to a team Source: https://docs.hexclave.com/api/server/teams/send-an-email-to-invite-a-user-to-a-team /openapi/server.json post /team-invitations/send-code The user receiving this email can join the team by clicking on the link in the email. If the user does not have an account yet, they will be prompted to create one. # Update a team Source: https://docs.hexclave.com/api/server/teams/update-a-team /openapi/server.json patch /teams/{team_id} Update the team information by ID. # Update a team member profile Source: https://docs.hexclave.com/api/server/teams/update-a-team-member-profile /openapi/server.json patch /team-member-profiles/{team_id}/{user_id} Update a team member profile by user ID # Create user Source: https://docs.hexclave.com/api/server/users/create-user /openapi/server.json post /users Creates a new user. E-mail authentication is always enabled, and no password is set, meaning the only way to authenticate the newly created user is through magic link. # Delete current user Source: https://docs.hexclave.com/api/server/users/delete-current-user /openapi/server.json delete /users/me Deletes the currently authenticated user. Use this with caution. # Delete user Source: https://docs.hexclave.com/api/server/users/delete-user /openapi/server.json delete /users/{user_id} Deletes a user. Use this with caution. # Get current user Source: https://docs.hexclave.com/api/server/users/get-current-user /openapi/server.json get /users/me Gets the currently authenticated user. # Get user Source: https://docs.hexclave.com/api/server/users/get-user /openapi/server.json get /users/{user_id} Gets a user by user ID. # List users Source: https://docs.hexclave.com/api/server/users/list-users /openapi/server.json get /users Lists all the users in the project. By default, only fully onboarded users are returned. Restricted users (those who haven't completed onboarding requirements like email verification) are included if `include_restricted` is set to `true`. Anonymous users are included if `include_anonymous` is set to `true` (which also includes restricted users). # Update current user Source: https://docs.hexclave.com/api/server/users/update-current-user /openapi/server.json patch /users/me Updates the currently authenticated user. Only the values provided will be updated. # Update user Source: https://docs.hexclave.com/api/server/users/update-user /openapi/server.json patch /users/{user_id} Updates a user. Only the values provided will be updated. # Team membershipcreated Source: https://docs.hexclave.com/api/webhooks/teams/team_membershipcreated /openapi/webhooks.json webhook team_membership.created This event is triggered when a user is added to a team. # Team membershipdeleted Source: https://docs.hexclave.com/api/webhooks/teams/team_membershipdeleted /openapi/webhooks.json webhook team_membership.deleted This event is triggered when a user is removed from a team. # Team permissioncreated Source: https://docs.hexclave.com/api/webhooks/teams/team_permissioncreated /openapi/webhooks.json webhook team_permission.created This event is triggered when a team permission is created. # Team permissiondeleted Source: https://docs.hexclave.com/api/webhooks/teams/team_permissiondeleted /openapi/webhooks.json webhook team_permission.deleted This event is triggered when a team permission is deleted. # Teamcreated Source: https://docs.hexclave.com/api/webhooks/teams/teamcreated /openapi/webhooks.json webhook team.created This event is triggered when a team is created. # Teamdeleted Source: https://docs.hexclave.com/api/webhooks/teams/teamdeleted /openapi/webhooks.json webhook team.deleted This event is triggered when a team is deleted. # Teamupdated Source: https://docs.hexclave.com/api/webhooks/teams/teamupdated /openapi/webhooks.json webhook team.updated This event is triggered when a team is updated. # Usercreated Source: https://docs.hexclave.com/api/webhooks/users/usercreated /openapi/webhooks.json webhook user.created This event is triggered when a user is created. # Userdeleted Source: https://docs.hexclave.com/api/webhooks/users/userdeleted /openapi/webhooks.json webhook user.deleted This event is triggered when a user is deleted. # Userupdated Source: https://docs.hexclave.com/api/webhooks/users/userupdated /openapi/webhooks.json webhook user.updated This event is triggered when a user is updated. # Analytics Source: https://docs.hexclave.com/guides/apps/analytics/overview Explore events, session replays, and SQL queries in your project's analytics dataset The Analytics app gives you direct access to your project's analytics dataset in Hexclave. You can inspect raw event tables, run ClickHouse SQL queries, and watch session replays to debug real user behavior. ## Overview Analytics is organized into three areas in the dashboard: * **Tables**: Browse event rows with sorting, search, and incremental loading * **Queries**: Run and save reusable ClickHouse SQL queries * **Replays**: Watch session replays and filter by user, team, duration, activity window, and click count ## How Analytics Works Stack records analytics events and replay chunks, then exposes them through the Analytics app for read-only querying and investigation. User activity in your app flows into Stack event ingestion, which stores data in ClickHouse. This data powers the Tables view, the SQL query runner, and the Session replay UI. ### What Gets Tracked Stack collects both client-side and server-side analytics events: * **Client-side events**: browser interaction events like `$page-view` and `$click` * **Server-side events**: currently `$token-refresh` and `$sign-up-rule-trigger` ## Enabling the Analytics App To use analytics in your project: 1. Open your Hexclave dashboard 2. Go to **Apps** 3. Open **Analytics** 4. Click **Enable** ## Quick Start 1. Enable Analytics in your Hexclave dashboard (**Apps -> Analytics**) 2. Initialize Hexclave on your frontend with `HexclaveClientApp`/`HexclaveProvider` 3. Sign in with a real user session 4. Open the app and navigate/click around 5. Check **Analytics -> Tables** to confirm events are arriving After setup, Stack automatically captures client-side `$page-view` and `$click` events. If you want replay recordings, also enable `analytics.replays.enabled` in your client app config. ## Tables The **Tables** screen is the fastest way to inspect recent analytics records. * Currently shows the `events` table * Built-in ordering and client-side search * Relative/absolute timestamp display toggle * Row detail dialog for inspecting full JSON payloads Use this view when you need to quickly answer "what just happened?" without writing SQL. ## Queries The **Queries** screen is a ClickHouse SQL workspace for deeper analysis. * Run read-only SQL queries with a timeout budget * Query the users and analytics tables * Save reusable queries into folders * Re-run saved queries with one click * Edit and overwrite saved query definitions ## Session Replays The **Replays** screen helps you move from "an event happened" to "what the user actually saw." * Filter sessions by user, team, duration, recency, and click count * Play back multi-tab sessions * Control playback speed * Optionally skip inactive ranges * Jump across click/page-view timeline markers Use replays when metrics alone are not enough to explain user behavior. ### Enabling Replay Recording in the SDK Session replay recording is disabled by default. To enable it, pass `analytics.replays.enabled: true` when creating your client app. ```ts theme={null} import { HexclaveClientApp } from "@hexclave/js"; export const hexclaveClientApp = new HexclaveClientApp({ // ...your existing client app options tokenStore: "cookie", // use "nextjs-cookie" in Next.js analytics: { replays: { enabled: true, // Optional. Defaults to true. maskAllInputs: true, }, }, }); ``` `maskAllInputs` defaults to `true`, so form fields are masked unless you explicitly disable it. ### Disabling Analytics Capture in the SDK SDK-managed analytics capture is enabled by default. You can disable it by disabling the Analytics app in the config or dashboard. If you don't want the SDK to collect any analytics data at all but would like to keep the Analytics app enabled, you can also pass `analytics: { enabled: false }` when creating your client app: ```ts theme={null} import { HexclaveClientApp } from "@hexclave/js"; export const hexclaveClientApp = new HexclaveClientApp({ // ...your existing client app options tokenStore: "cookie", // use "nextjs-cookie" in Next.js analytics: { enabled: false }, }); ``` This stops the SDK from sending `$page-view` and `$click` events. If you'd rather keep analytics, enable the Analytics app in your dashboard (**Apps -> Analytics**) instead. ## Best Practices 1. **Use Tables for quick incident triage**: the Tables UI is the fastest way to inspect recent `events` rows without writing SQL. 2. **Use Queries for repeatable analysis**: save important SQL in folders, and scope queries with filters/`LIMIT` so they stay within result and timeout limits. 3. **Use Replays for behavioral debugging**: start from an event pattern, then inspect matching session replays to understand what users actually did. 4. **Keep replay privacy defaults on**: leave `maskAllInputs` enabled unless you have a specific reason and a data-handling policy for unmasked inputs. # API Keys Source: https://docs.hexclave.com/guides/apps/api-keys/overview Create and manage API keys for users and teams The API Keys app enables your users to generate and manage API keys for programmatic access to your backend services. API keys provide a secure way to authenticate requests, allowing developers to associate API calls with specific users or teams. Hexclave provides prebuilt UI components for users and teams to manage their own API keys. ## Overview API keys allow your users to access your backend services programmatically without interactive authentication. The flow works as follows: a user or client sends an API request with an API key to your application server. Your server validates the API key with Hexclave, which returns an authenticated User object. Your server then processes the request and returns the response. Hexclave provides two types of API keys: ### User API keys User API keys are associated with individual users and allow them to authenticate with your API. ```typescript title="app/components/create-api-key.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export default function CreateApiKey() { const user = useUser({ or: 'redirect' }); const handleCreateKey = async () => { const apiKey = await user.createApiKey({ description: "My client application", expiresAt: new Date(Date.now() + (90 * 24 * 60 * 60 * 1000)), // 90 days }); console.log("API Key created:", apiKey.value); }; return ; } ``` ```typescript title="app/components/create-api-key.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function CreateApiKey() { const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const apiKey = await user.createApiKey({ description: "Admin-provisioned API key", expiresAt: new Date(Date.now() + (30 * 24 * 60 * 60 * 1000)), // 30 days }); return
API Key: {apiKey.value}
; } ```
```typescript title="components/CreateApiKey.tsx" theme={null} "use client"; import { useUser } from "@hexclave/react"; export default function CreateApiKey() { const user = useUser({ or: 'redirect' }); const handleCreateKey = async () => { const apiKey = await user.createApiKey({ description: "My client application", expiresAt: new Date(Date.now() + (90 * 24 * 60 * 60 * 1000)), // 90 days }); console.log("API Key created:", apiKey.value); }; return ; } ``` ```python title="views.py" theme={null} import requests from django.http import JsonResponse def create_user_api_key(request): # Get the current user's access token from session/cookie access_token = request.COOKIES.get('stack-access-token') # Create API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/user-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'user_id': 'me', 'description': 'My client application', 'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: raise Exception(f"Failed to create API key: {response.text}") return JsonResponse(response.json()) ``` ```python title="main.py" theme={null} import requests import time from fastapi import Cookie, HTTPException @app.post("/api/create-user-api-key") async def create_user_api_key(stack_access_token: str = Cookie(None, alias="stack-access-token")): if not stack_access_token: raise HTTPException(status_code=401, detail="Not authenticated") # Create API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/user-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': stack_access_token, }, json={ 'user_id': 'me', 'description': 'My client application', 'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: raise HTTPException(status_code=response.status_code, detail=response.text) return response.json() ``` ```python title="app.py" theme={null} import requests import time from flask import request, jsonify @app.route('/api/create-user-api-key', methods=['POST']) def create_user_api_key(): access_token = request.cookies.get('stack-access-token') if not access_token: return jsonify({'error': 'Not authenticated'}), 401 # Create API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/user-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'user_id': 'me', 'description': 'My client application', 'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: return jsonify({'error': response.text}), response.status_code return jsonify(response.json()) ```
### Team API keys Team API keys are associated with teams and can be used to provide access to team resources over your API. ```typescript title="app/components/create-team-api-key.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export default function CreateTeamApiKey({ teamId }: { teamId: string }) { const user = useUser({ or: 'redirect' }); const team = user.useTeam(teamId); const handleCreateKey = async () => { if (!team) return; const teamApiKey = await team.createApiKey({ description: "Team integration service", expiresAt: new Date(Date.now() + (60 * 24 * 60 * 60 * 1000)), // 60 days }); console.log("Team API Key created:", teamApiKey.value); }; return ; } ``` ```typescript title="app/components/create-team-api-key.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function CreateTeamApiKey({ teamId }: { teamId: string }) { const team = await hexclaveServerApp.getTeam(teamId); if (!team) { return
Team not found
; } const teamApiKey = await team.createApiKey({ description: "Admin-provisioned team API key", expiresAt: new Date(Date.now() + (30 * 24 * 60 * 60 * 1000)), // 30 days }); return
Team API Key: {teamApiKey.value}
; } ```
```typescript title="components/CreateTeamApiKey.tsx" theme={null} "use client"; import { useUser } from "@hexclave/react"; export default function CreateTeamApiKey({ teamId }: { teamId: string }) { const user = useUser({ or: 'redirect' }); const team = user.useTeam(teamId); const handleCreateKey = async () => { if (!team) return; const teamApiKey = await team.createApiKey({ description: "Team integration service", expiresAt: new Date(Date.now() + (60 * 24 * 60 * 60 * 1000)), // 60 days }); console.log("Team API Key created:", teamApiKey.value); }; return ; } ``` ```python title="views.py" theme={null} import requests import time from django.http import JsonResponse def create_team_api_key(request, team_id): # Get the current user's access token from session/cookie access_token = request.COOKIES.get('stack-access-token') # Create team API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/team-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'team_id': team_id, 'description': 'Team integration service', 'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: raise Exception(f"Failed to create team API key: {response.text}") return JsonResponse(response.json()) ``` ```python title="main.py" theme={null} import requests import time from fastapi import Cookie, HTTPException @app.post("/api/teams/{team_id}/api-keys") async def create_team_api_key(team_id: str, stack_access_token: str = Cookie(None, alias="stack-access-token")): if not stack_access_token: raise HTTPException(status_code=401, detail="Not authenticated") # Create team API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/team-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': stack_access_token, }, json={ 'team_id': team_id, 'description': 'Team integration service', 'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: raise HTTPException(status_code=response.status_code, detail=response.text) return response.json() ``` ```python title="app.py" theme={null} import requests import time from flask import request, jsonify @app.route('/api/teams//api-keys', methods=['POST']) def create_team_api_key(team_id): access_token = request.cookies.get('stack-access-token') if not access_token: return jsonify({'error': 'Not authenticated'}), 401 # Create team API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/team-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'team_id': team_id, 'description': 'Team integration service', 'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: return jsonify({'error': response.text}), response.status_code return jsonify(response.json()) ```
## Enabling the API Keys App To use API keys in your application, you need to enable the API Keys app in your Hexclave dashboard: 1. Navigate to your Hexclave dashboard 2. Go to the **Apps** section 3. Find and click on **API Keys** in the app store 4. Click the **Enable** button Once enabled, you can configure User API Keys and Team API Keys in the app settings. The app will provide your users with a prebuilt UI to manage their own API keys. ## Prebuilt UI Components Hexclave provides prebuilt UI components that allow your users to manage their own API keys without any additional code: ### User API Keys UI For frameworks that support React components, the `` component includes an API Keys tab where users can: * View all their active API keys * Create new API keys with custom descriptions and expiration dates * Revoke existing API keys * See when each key was created and when it expires. ```typescript title="app/src/account-page.tsx" theme={null} import { AccountSettings } from '@hexclave/next'; export default function MyAccountPage() { return ( ); } ``` ```typescript title="app/src/account-page.tsx" theme={null} import { AccountSettings } from '@hexclave/react'; export default function MyAccountPage() { return ( ); } ``` ### Team API Keys UI For team API keys, the team settings page automatically includes an API Keys section when: * The API Keys app is enabled * `allowTeamApiKeys` is configured in your project settings * The user has the `$manage_api_keys` permission for the team Users with appropriate permissions can manage team API keys directly from the team settings interface. ## Working with API Keys ### Creating User API Keys ```typescript title="app/components/create-api-key.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export default function CreateApiKey() { const user = useUser({ or: 'redirect' }); const handleCreateKey = async () => { const apiKey = await user.createApiKey({ description: "My client application", expiresAt: new Date(Date.now() + (90 * 24 * 60 * 60 * 1000)), // 90 days }); console.log("API Key created:", apiKey.value); }; return ; } ``` ```typescript title="app/components/create-api-key.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function CreateApiKey() { const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const apiKey = await user.createApiKey({ description: "Admin-provisioned API key", expiresAt: new Date(Date.now() + (30 * 24 * 60 * 60 * 1000)), // 30 days }); return
API Key: {apiKey.value}
; } ```
```typescript title="components/CreateApiKey.tsx" theme={null} "use client"; import { useUser } from "@hexclave/react"; export default function CreateApiKey() { const user = useUser({ or: 'redirect' }); const handleCreateKey = async () => { const apiKey = await user.createApiKey({ description: "My client application", expiresAt: new Date(Date.now() + (90 * 24 * 60 * 60 * 1000)), // 90 days }); console.log("API Key created:", apiKey.value); }; return ; } ``` ```python title="views.py" theme={null} import requests from django.http import JsonResponse def create_user_api_key(request): # Get the current user's access token from session/cookie access_token = request.COOKIES.get('stack-access-token') # Create API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/user-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'user_id': 'me', 'description': 'My client application', 'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: raise Exception(f"Failed to create API key: {response.text}") return JsonResponse(response.json()) ``` ```python title="main.py" theme={null} import requests import time from fastapi import Cookie, HTTPException @app.post("/api/create-user-api-key") async def create_user_api_key(stack_access_token: str = Cookie(None, alias="stack-access-token")): if not stack_access_token: raise HTTPException(status_code=401, detail="Not authenticated") # Create API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/user-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': stack_access_token, }, json={ 'user_id': 'me', 'description': 'My client application', 'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: raise HTTPException(status_code=response.status_code, detail=response.text) return response.json() ``` ```python title="app.py" theme={null} import requests import time from flask import request, jsonify @app.route('/api/create-user-api-key', methods=['POST']) def create_user_api_key(): access_token = request.cookies.get('stack-access-token') if not access_token: return jsonify({'error': 'Not authenticated'}), 401 # Create API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/user-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'user_id': 'me', 'description': 'My client application', 'expires_at_millis': int((time.time() + 90 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: return jsonify({'error': response.text}), response.status_code return jsonify(response.json()) ```
### Creating Team API Keys ```typescript title="app/components/create-team-api-key.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export default function CreateTeamApiKey({ teamId }: { teamId: string }) { const user = useUser({ or: 'redirect' }); const team = user.useTeam(teamId); const handleCreateKey = async () => { if (!team) return; const teamApiKey = await team.createApiKey({ description: "Team integration service", expiresAt: new Date(Date.now() + (60 * 24 * 60 * 60 * 1000)), // 60 days }); console.log("Team API Key created:", teamApiKey.value); }; return ; } ``` ```typescript title="app/components/create-team-api-key.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function CreateTeamApiKey({ teamId }: { teamId: string }) { const team = await hexclaveServerApp.getTeam(teamId); if (!team) { return
Team not found
; } const teamApiKey = await team.createApiKey({ description: "Admin-provisioned team API key", expiresAt: new Date(Date.now() + (30 * 24 * 60 * 60 * 1000)), // 30 days }); return
Team API Key: {teamApiKey.value}
; } ```
```typescript title="components/CreateTeamApiKey.tsx" theme={null} "use client"; import { useUser } from "@hexclave/react"; export default function CreateTeamApiKey({ teamId }: { teamId: string }) { const user = useUser({ or: 'redirect' }); const team = user.useTeam(teamId); const handleCreateKey = async () => { if (!team) return; const teamApiKey = await team.createApiKey({ description: "Team integration service", expiresAt: new Date(Date.now() + (60 * 24 * 60 * 60 * 1000)), // 60 days }); console.log("Team API Key created:", teamApiKey.value); }; return ; } ``` ```python title="views.py" theme={null} import requests import time from django.http import JsonResponse def create_team_api_key(request, team_id): # Get the current user's access token from session/cookie access_token = request.COOKIES.get('stack-access-token') # Create team API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/team-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'team_id': team_id, 'description': 'Team integration service', 'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: raise Exception(f"Failed to create team API key: {response.text}") return JsonResponse(response.json()) ``` ```python title="main.py" theme={null} import requests import time from fastapi import Cookie, HTTPException @app.post("/api/teams/{team_id}/api-keys") async def create_team_api_key(team_id: str, stack_access_token: str = Cookie(None, alias="stack-access-token")): if not stack_access_token: raise HTTPException(status_code=401, detail="Not authenticated") # Create team API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/team-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': stack_access_token, }, json={ 'team_id': team_id, 'description': 'Team integration service', 'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: raise HTTPException(status_code=response.status_code, detail=response.text) return response.json() ``` ```python title="app.py" theme={null} import requests import time from flask import request, jsonify @app.route('/api/teams//api-keys', methods=['POST']) def create_team_api_key(team_id): access_token = request.cookies.get('stack-access-token') if not access_token: return jsonify({'error': 'Not authenticated'}), 401 # Create team API key via client API response = requests.post( 'https://api.hexclave.com/api/v1/team-api-keys', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'team_id': team_id, 'description': 'Team integration service', 'expires_at_millis': int((time.time() + 60 * 24 * 60 * 60) * 1000), } ) if response.status_code != 200: return jsonify({'error': response.text}), response.status_code return jsonify(response.json()) ```
### Listing API Keys ```typescript title="app/components/api-keys-list.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export default function ApiKeysList() { const user = useUser({ or: 'redirect' }); const apiKeys = user.useApiKeys(); return (

Your API Keys

{apiKeys.map(key => (

{key.description}

Last 4 digits: {key.value.lastFour}

Created: {key.createdAt.toLocaleDateString()}

))}
); } ```
```typescript title="app/components/api-keys-list.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function ApiKeysList() { const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const apiKeys = await user.listApiKeys(); return (

Your API Keys

{apiKeys.map(key => (

{key.description}

Last 4 digits: {key.value.lastFour}

Created: {key.createdAt.toLocaleDateString()}

))}
); } ```
```typescript title="components/ApiKeysList.tsx" theme={null} "use client"; import { useUser } from "@hexclave/react"; export default function ApiKeysList() { const user = useUser({ or: 'redirect' }); const apiKeys = user.useApiKeys(); return (

Your API Keys

{apiKeys.map(key => (

{key.description}

Last 4 digits: {key.value.lastFour}

Created: {key.createdAt.toLocaleDateString()}

))}
); } ```
```python title="views.py" theme={null} import requests from django.http import JsonResponse def list_user_api_keys(request): # Get the current user's access token from session/cookie access_token = request.COOKIES.get('stack-access-token') # List user's API keys via client API response = requests.get( 'https://api.hexclave.com/api/v1/user-api-keys?user_id=me', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, } ) if response.status_code != 200: raise Exception(f"Failed to list API keys: {response.text}") return JsonResponse(response.json(), safe=False) ``` ```python title="main.py" theme={null} import requests from fastapi import Cookie, HTTPException @app.get("/api/user-api-keys") async def list_user_api_keys(stack_access_token: str = Cookie(None, alias="stack-access-token")): if not stack_access_token: raise HTTPException(status_code=401, detail="Not authenticated") # List user's API keys via client API response = requests.get( 'https://api.hexclave.com/api/v1/user-api-keys?user_id=me', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': stack_access_token, } ) if response.status_code != 200: raise HTTPException(status_code=response.status_code, detail=response.text) return response.json() ``` ```python title="app.py" theme={null} import requests from flask import request, jsonify @app.route('/api/user-api-keys', methods=['GET']) def list_user_api_keys(): access_token = request.cookies.get('stack-access-token') if not access_token: return jsonify({'error': 'Not authenticated'}), 401 # List user's API keys via client API response = requests.get( 'https://api.hexclave.com/api/v1/user-api-keys?user_id=me', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, } ) if response.status_code != 200: return jsonify({'error': response.text}), response.status_code return jsonify(response.json()) ```
### Revoking API Keys API keys can be revoked when they are no longer needed or if they have been compromised. ```typescript title="app/components/revoke-api-key.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export default function RevokeApiKey({ apiKeyId }: { apiKeyId: string }) { const user = useUser({ or: 'redirect' }); const apiKeys = user.useApiKeys(); const handleRevoke = async () => { const apiKeyToRevoke = apiKeys.find(key => key.id === apiKeyId); if (apiKeyToRevoke) { await apiKeyToRevoke.revoke(); console.log("API Key revoked"); } }; return ; } ``` ```typescript title="lib/api-keys.ts" theme={null} import { hexclaveServerApp } from "@/stack/server"; export async function revokeApiKey(userId: string, apiKeyId: string) { const user = await hexclaveServerApp.getUser(userId); if (!user) return; const apiKeys = await user.listApiKeys(); const apiKeyToRevoke = apiKeys.find(key => key.id === apiKeyId); if (apiKeyToRevoke) { await apiKeyToRevoke.revoke(); } } ``` ```typescript title="components/RevokeApiKey.tsx" theme={null} "use client"; import { useUser } from "@hexclave/react"; export default function RevokeApiKey({ apiKeyId }: { apiKeyId: string }) { const user = useUser({ or: 'redirect' }); const apiKeys = user.useApiKeys(); const handleRevoke = async () => { const apiKeyToRevoke = apiKeys.find(key => key.id === apiKeyId); if (apiKeyToRevoke) { await apiKeyToRevoke.revoke(); console.log("API Key revoked"); } }; return ; } ``` ```python title="views.py" theme={null} import requests from django.http import JsonResponse def revoke_api_key(request, api_key_id): # Get the current user's access token from session/cookie access_token = request.COOKIES.get('stack-access-token') # Revoke API key via client API (update with revoked: true) response = requests.patch( f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'revoked': True, } ) if response.status_code != 200: raise Exception(f"Failed to revoke API key: {response.text}") return JsonResponse({'message': 'API key revoked successfully'}) ``` ```python title="main.py" theme={null} import requests from fastapi import Cookie, HTTPException @app.delete("/api/user-api-keys/{api_key_id}") async def revoke_api_key(api_key_id: str, stack_access_token: str = Cookie(None, alias="stack-access-token")): if not stack_access_token: raise HTTPException(status_code=401, detail="Not authenticated") # Revoke API key via client API (update with revoked: true) response = requests.patch( f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': stack_access_token, }, json={ 'revoked': True, } ) if response.status_code != 200: raise HTTPException(status_code=response.status_code, detail=response.text) return {"message": "API key revoked successfully"} ``` ```python title="app.py" theme={null} import requests from flask import request, jsonify @app.route('/api/user-api-keys/', methods=['DELETE']) def revoke_api_key(api_key_id): access_token = request.cookies.get('stack-access-token') if not access_token: return jsonify({'error': 'Not authenticated'}), 401 # Revoke API key via client API (update with revoked: true) response = requests.patch( f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, }, json={ 'revoked': True, } ) if response.status_code != 200: return jsonify({'error': response.text}), response.status_code return jsonify({'message': 'API key revoked successfully'}) ``` ### Checking API Key Validity You can check if an API key is still valid: ```typescript title="app/components/check-api-key.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export default function CheckApiKeyValidity({ apiKeyId }: { apiKeyId: string }) { const user = useUser({ or: 'redirect' }); const apiKeys = user.useApiKeys(); const apiKey = apiKeys.find(key => key.id === apiKeyId); if (!apiKey) { return
API key not found
; } if (apiKey.isValid()) { return
API key is valid
; } const reason = apiKey.whyInvalid(); return
API key is invalid: {reason}
; } ```
```typescript title="app/components/check-api-key.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function CheckApiKeyValidity({ userId, apiKeyId }: { userId: string, apiKeyId: string }) { const user = await hexclaveServerApp.getUser(userId); if (!user) return
User not found
; const apiKeys = await user.listApiKeys(); const apiKey = apiKeys.find(key => key.id === apiKeyId); if (!apiKey) { return
API key not found
; } if (apiKey.isValid()) { return
API key is valid
; } const reason = apiKey.whyInvalid(); return
API key is invalid: {reason}
; } ```
```typescript title="components/CheckApiKey.tsx" theme={null} "use client"; import { useUser } from "@hexclave/react"; export default function CheckApiKeyValidity({ apiKeyId }: { apiKeyId: string }) { const user = useUser({ or: 'redirect' }); const apiKeys = user.useApiKeys(); const apiKey = apiKeys.find(key => key.id === apiKeyId); if (!apiKey) { return
API key not found
; } if (apiKey.isValid()) { return
API key is valid
; } const reason = apiKey.whyInvalid(); return
API key is invalid: {reason}
; } ```
```python title="views.py" theme={null} import requests import time from django.http import JsonResponse def check_api_key_validity(request, api_key_id): # Get the current user's access token from session/cookie access_token = request.COOKIES.get('stack-access-token') # Get API key details via client API response = requests.get( f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, } ) if response.status_code != 200: return JsonResponse({'error': 'API key not found'}, status=404) api_key = response.json() # Check if manually revoked if api_key.get('manually_revoked_at_millis'): return JsonResponse({ 'valid': False, 'reason': 'manually-revoked' }) # Check if expired if api_key.get('expires_at_millis'): if api_key['expires_at_millis'] < time.time() * 1000: return JsonResponse({ 'valid': False, 'reason': 'expired' }) return JsonResponse({'valid': True}) ``` ```python title="main.py" theme={null} import requests import time from fastapi import Cookie, HTTPException @app.get("/api/check-api-key/{api_key_id}") async def check_api_key_validity(api_key_id: str, stack_access_token: str = Cookie(None, alias="stack-access-token")): if not stack_access_token: raise HTTPException(status_code=401, detail="Not authenticated") # Get API key details via client API response = requests.get( f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': stack_access_token, } ) if response.status_code != 200: raise HTTPException(status_code=404, detail="API key not found") api_key = response.json() # Check if manually revoked if api_key.get('manually_revoked_at_millis'): return { 'valid': False, 'reason': 'manually-revoked' } # Check if expired if api_key.get('expires_at_millis'): if api_key['expires_at_millis'] < time.time() * 1000: return { 'valid': False, 'reason': 'expired' } return {'valid': True} ``` ```python title="app.py" theme={null} import requests import time from flask import request, jsonify @app.route('/api/check-api-key/', methods=['GET']) def check_api_key_validity(api_key_id): access_token = request.cookies.get('stack-access-token') if not access_token: return jsonify({'error': 'Not authenticated'}), 401 # Get API key details via client API response = requests.get( f'https://api.hexclave.com/api/v1/user-api-keys/{api_key_id}', headers={ 'x-stack-access-type': 'client', 'x-stack-project-id': stack_project_id, 'x-stack-publishable-client-key': stack_publishable_client_key, 'x-stack-access-token': access_token, } ) if response.status_code != 200: return jsonify({'error': 'API key not found'}), 404 api_key = response.json() # Check if manually revoked if api_key.get('manually_revoked_at_millis'): return jsonify({ 'valid': False, 'reason': 'manually-revoked' }) # Check if expired if api_key.get('expires_at_millis'): if api_key['expires_at_millis'] < time.time() * 1000: return jsonify({ 'valid': False, 'reason': 'expired' }) return jsonify({'valid': True}) ```
# All Auth Providers Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers Configure authentication providers for your application Hexclave supports a variety of authentication providers to give your users flexible sign-in options. You can configure these providers through the Hexclave dashboard. ## Overview Authentication providers determine how users can sign in to your application. Stack supports the following provider types: * **Email/Password**: Traditional email and password authentication * **Magic Link**: Passwordless authentication via email links * **OAuth Providers**: Third-party sign-in with providers like Google, GitHub, Facebook, Microsoft, and more * **Passkeys**: WebAuthn-based passwordless authentication ## Configuring Providers Navigate to your project in the Hexclave dashboard. Click on the **Auth Providers** section in the sidebar. Toggle the providers you want to enable for your application. For OAuth providers, you can use Stack's shared keys for development or configure your own OAuth client ID and client secret for production. ## Shared vs. Custom OAuth Keys For development and testing, Stack provides shared OAuth keys that work out of the box. For production, you should set up your own OAuth client credentials. ### Shared Keys Shared keys allow you to quickly get started without needing to register your application with each OAuth provider. These are suitable for development only. ### Custom Keys For production use, configure your own OAuth client ID and client secret for each provider: 1. Register your application with the OAuth provider (e.g., Google Cloud Console, GitHub Developer Settings) 2. Obtain the client ID and client secret 3. Enter them in the Hexclave dashboard under the respective provider settings ## OAuth Providers Sign in with GitHub Sign in with Google Sign in with Facebook Sign in with Microsoft Sign in with Spotify Sign in with Discord Sign in with GitLab Sign in with Apple Sign in with Bitbucket Sign in with LinkedIn Sign in with X Sign in with Twitch ## Other Authentication Methods WebAuthn-based passwordless authentication TOTP-based two-factor authentication ## Going to Production When preparing your application for production, make sure to: * Replace shared OAuth keys with your own client credentials * Configure a custom email server for email-based authentication * Set up proper redirect URLs for OAuth providers For more details, see [Going to Production](/guides/apps/launch-checklist/overview). # Apple Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/apple Set up Apple as an authentication provider with Hexclave This guide explains how to set up Apple as an authentication provider with Hexclave. Sign in with Apple allows users to sign in to your application using their Apple ID. You will need to create an Apple Developer account, and generate an Apple Services ID, Apple Private Key, Apple Team ID, and Apple Key ID. ## Integration Steps ### Create an Apple App ID and Services ID 1. Log in to the [Apple Developer Portal](https://developer.apple.com/). 2. Navigate to **Certificates, IDs & Profiles**. 3. In the sidebar, select **Identifiers** and click the "+" button to register a new identifier. 4. Select **App IDs** and click **Continue**. 5. Select **App** as the type and click **Continue**. 6. Give your app a description and a Bundle ID (e.g., com.yourdomain.app). 7. Scroll down and enable **Sign in with Apple**, then click **Continue**, then **Register**. 8. In the top-right of the Identifiers page, switch to **Services IDs**. 9. Click the "+" button to create a new Service ID and click **Continue**. 10. Give it a description and an identifier (note: this cannot be the same as your App ID's bundle ID). 11. Click **Continue**, then **Register**. 12. From the list, select your new Service ID. 13. Enable **Sign in with Apple** by checking the box. 14. Click **Configure** next to Sign in with Apple. 15. Register your domains (add api.hexclave.com). 16. Add the return URL: `https://api.hexclave.com/api/v1/auth/oauth/callback/apple` 17. Click **Done**, then **Continue**, and then **Save**. ### Create a Private Key 1. In the sidebar, select **Keys** and click the "+" button. 2. Give your key a name and usage description. 3. Scroll down to enable **Sign in with Apple** and click **Configure**. 4. Select your Primary App ID that you created earlier and click **Save**. 5. Click **Continue**, then **Register**. 6. On the next page, **download your key file (.p8)**. This is critical as you won't be able to download it again. 7. Note your **Key ID** displayed on this page. 8. Click **Done**. 9. Find your **Account ID** at the very top-right of the Apple Developer Portal page. ### Generate Your Client Secret Use the tool below to generate your Apple Client Secret. You'll need: * **Team ID**: Your Apple Developer account ID found at the top-right of the portal * **Service ID**: The identifier of your Service ID (found in Identifiers > Service IDs) * **Key ID**: The ID of the private key you just created * **Private Key File**: Upload the .p8 private key file you downloaded Copy the generated secret immediately - you'll need it for the next step. ### Enable Apple OAuth in Hexclave 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **Apple** as the provider. 3. Set the **Client ID** (your Service ID identifier), **Client Secret** (the generated secret from Supabase), and **Team ID** (your Apple Developer Team ID). ## Native App Configuration (iOS/macOS) Native iOS and macOS apps using the Hexclave Swift SDK require Bundle ID configuration in addition to the web OAuth setup above. Native apps use Apple's native Sign in with Apple flow (`ASAuthorizationController`) instead of web-based OAuth. Bundle IDs are only required for native iOS/macOS apps. Web applications only need the Service ID configuration described above. ### Add Your Bundle IDs 1. On the Hexclave dashboard, navigate to **Auth Methods** and select your Apple provider. 2. In the Apple configuration modal, add your app's **Bundle ID** (e.g., `com.yourdomain.app`). This is the same Bundle ID from your App ID in Apple Developer Portal (Step 1 above). 3. If you have multiple apps (e.g., separate iOS and macOS apps), add all their Bundle IDs. 4. Click **Save**. Your native app can now use `signInWithOAuth(provider: "apple")` from the Swift SDK. ### Need More Help? * Check the [Sign in with Apple Documentation](https://developer.apple.com/sign-in-with-apple/get-started/) * Join our [Discord](https://discord.hexclave.com) # Bitbucket Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/bitbucket Set up Bitbucket as an authentication provider with Hexclave This guide explains how to set up Bitbucket as an authentication provider with Hexclave. Bitbucket OAuth allows users to sign in to your application using their Bitbucket account. ## Integration Steps 1. Log in to your [Bitbucket Workspaces account](https://bitbucket.org/account/workspaces/). 2. Under **Workspaces**, find your workspace and select **Manage**. 3. In the left sidebar, scroll down and select **OAuth consumers**. 4. Click **Add consumer**. 5. Fill out the form with the following details: * **Name**: Choose a name for your application * **Description**: Add a brief description of your application * **Callback URL**: Enter `https://api.hexclave.com/api/v1/auth/oauth/callback/bitbucket` * **Permissions**: Under **Account**, select at minimum **Email** and **Read** 6. Click **Save**. 7. You'll be redirected to the OAuth consumers page. Select your newly created consumer to view its details. 8. Note the **Key** (Client ID) and **Secret** values. Save these somewhere secure as you'll need them for the next steps. 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **Bitbucket** as the provider. 3. Set the **Client ID** (the Key from your Bitbucket OAuth consumer) and **Client Secret** you obtained from Bitbucket earlier. ### Need More Help? * Check the [Bitbucket OAuth documentation](https://developer.atlassian.com/cloud/bitbucket/oauth-2/) * Join our [Discord](https://discord.hexclave.com) # Discord Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/discord Set up Discord as an authentication provider with Hexclave This guide explains how to set up Discord as an authentication provider with Hexclave. Discord OAuth2 allows users to sign in to your application using their Discord account. ## Integration Steps 1. Navigate to the [Discord Developer Portal](https://discord.com/developers/applications). 2. Click the **New Application** button in the top-right corner. 3. Enter a name for your application and click **Create**. You will be redirected to the General Information page. 4. Select **OAuth2** in the left sidebar. 5. Under **Redirects** add `https://api.hexclave.com/api/v1/auth/oauth/callback/discord` 6. In the **OAuth2** section, enable the required scopes: 'identify' and 'email' 7. Click **Save Changes** 8. Save the **Client ID** and **Client Secret**. You may need to select **Reset Secret** to generate a new one. 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **Discord** as the provider. 3. Set the **Client ID** and **Client Secret** you obtained from the Discord Developer Portal earlier. ### User Profile Data When a user signs in with Discord, Hexclave will create a user profile with the following data: * **User ID**: Discord's unique user ID * **Username**: The user's Discord username * **Avatar**: The user's Discord avatar (if available) * **Email**: The user's email if the 'email' scope is requested ### Need More Help? * Check the [Discord OAuth2 Documentation](https://discord.com/developers/docs/topics/oauth2) * Visit our [Discord Support Channel](https://discord.hexclave.com) # Facebook Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/facebook Set up Facebook as an authentication provider with Hexclave This guide explains how to set up Facebook as an authentication provider with Hexclave. Facebook OAuth allows users to sign in to your application using their Facebook account. ## Integration Steps 1. Navigate to the [Facebook Developers Portal](https://developers.facebook.com/). 2. In the top-right, select **My Apps** and then **Create App**. 3. You'll be redirected to the Create an app process. 4. In the **App details** step, select the app type (typically **Consumer** for authentication), fill out the necessary information, and select **Next**. 5. In the **Use Cases** step, select **Authenticate and request data from users with Facebook Login** and then select **Next**. 6. In the **Business** step, select the business portfolio to connect to your app and then select **Next**. 7. In the **Finalize** step, select **Go to dashboard**. You'll be redirected to the app's Dashboard page. 8. In the left sidenav, select **Use cases**. 9. Next to **Authenticate and request data from users with Facebook Login**, select **Customize**. 10. On the Permissions tab, next to **email**, select **Add** to allow Hexclave to read your user's primary email address. 11. In the left sidenav, under **Facebook Login**, select **Settings**. 12. In the **Client OAuth settings** section, in the **Valid OAuth Redirect URIs** field, add `https://api.hexclave.com/api/v1/auth/oauth/callback/facebook` 13. Select **Save changes**. 14. In the left sidenav, select **App settings** (hover over the settings icon), and then select **Basic**. 15. Note your **App ID** and **App Secret** for the next steps. 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **Facebook** as the provider. 3. Set the **App ID** and **App Secret** you obtained from the Facebook Developers Portal earlier. ### Need More Help? * Check the [Facebook Login Documentation](https://developers.facebook.com/docs/facebook-login/) * Join our [Discord](https://discord.hexclave.com) # GitHub Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/github Set up GitHub as an authentication provider with Hexclave This guide explains how to set up GitHub as an authentication provider with Hexclave. GitHub allows users to sign in to your Hexclave-enabled app using their GitHub account. For Development purposes, Hexclave uses shared keys for this provider. Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page. You should replace these before you go into production. ## Integration Steps If you are unsure if you need to create a GitHub App, or a GitHub OAuth App, check the [Differences On GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/differences-between-github-apps-and-oauth-apps). More than likely, you will want to create a GitHub App. The installation process is the same for both. ### Create a GitHub App 1. Navigate to your [GitHub Developer App Settings](https://github.com/settings/apps). 2. Click the **New GitHub App** button. 3. Enter a name for your application, homepage URL, and a description. 4. For **Authorization callback URL**, add `https://api.hexclave.com/api/v1/auth/oauth/callback/github` 5. For permissions, at a **minimum**, you will need **Account Permissions > Email Addresses** set to **Read Only**. Your sign-in flow will not work without this permission. 6. Select **Any Account** under the ***Where can this GitHub App be installed*** section. 7. Click **Create GitHub App** 8. Save the **Client ID** and click **Generate a new client secret** to create your **Client Secret**. ### Enable GitHub Provider in Hexclave 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **GitHub** as the provider. 3. Set the **Client ID** and **Client Secret** you obtained from your GitHub App earlier. *** ### Create an OAuth App 1. Navigate to your [GitHub Developer Settings](https://github.com/settings/developers). 2. Click the **New OAuth App** button. 3. Enter a name for your application, homepage URL, and a description. 4. For **Authorization callback URL**, add `https://api.hexclave.com/api/v1/auth/oauth/callback/github` 5. Click **Register application** 6. Save the **Client ID** and click **Generate a new client secret** to create your **Client Secret**. ### Enable GitHub OAuth in Hexclave 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **GitHub** as the provider. 3. Set the **Client ID** and **Client Secret** you obtained from GitHub earlier. ### Need More Help? * Check the [GitHub OAuth Documentation](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps) * Join our [Discord](https://discord.hexclave.com) # GitLab Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/gitlab Set up GitLab as an authentication provider with Hexclave This guide explains how to set up GitLab as an authentication provider with Hexclave. GitLab OAuth allows users to sign in to your application using their GitLab account. ## Integration Steps 1. Log in to your GitLab account. 2. In the top-right corner, click on your profile picture and select **Preferences**. 3. In the left sidebar, select **Applications** > **Add new application**. 4. Fill out the form with the following details: * **Name**: Choose a name for your application * **Redirect URI**: Enter `https://api.hexclave.com/api/v1/auth/oauth/callback/gitlab` * **Scopes**: Select at minimum the `profile` and `email` scopes 5. Click **Save application**. 6. GitLab will display your **Application ID** and **Secret**. Make note of these values as you'll need them for the next steps. 7. If you're using a self-hosted GitLab instance, you'll also need the URL of your GitLab instance. 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **GitLab** as the provider. 3. Set the **Application ID** and **Secret** you obtained from GitLab earlier. 4. If you're using a self-hosted GitLab instance, you'll also need to provide the URL for your instance. For gitlab.com, you can leave this field blank or use the default value. ### Need More Help? * Check the [GitLab OAuth 2.0 documentation](https://docs.gitlab.com/ee/api/oauth2.html) * Join our [Discord](https://discord.hexclave.com) # Google Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/google Set up Google as an authentication provider with Hexclave This guide explains how to set up Google as an authentication provider with Hexclave. Google OAuth2 allows users to sign in to your application using their Google account. For Development purposes, Hexclave uses shared keys for this provider. Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page. You should replace these before you go into production. ## Integration Steps ### Create a Google OAuth2 App 1. Navigate to the [Google Cloud Console](https://console.cloud.google.com/). 2. Create a new project or select an existing one. 3. In the sidebar, navigate to **APIs & Services** > **Credentials**. 4. Click **Create Credentials** and select **OAuth client ID**. 5. Select **Web application** as the application type. 6. Enter a name for your OAuth client. 7. Under **Authorized redirect URIs**, add `https://api.hexclave.com/api/v1/auth/oauth/callback/google` 8. Click **Create**. 9. Save the **Client ID** and **Client Secret** that are displayed. ### Enable Google OAuth2 in Hexclave 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **Google** as the provider. 3. Set the **Client ID** and **Client Secret** you obtained from Google Cloud Console earlier. ### Need More Help? * Check the [Google OAuth2 Documentation](https://developers.google.com/identity/protocols/oauth2) * Join our [Discord](https://discord.hexclave.com) # LinkedIn Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/linkedin Set up LinkedIn as an authentication provider with Hexclave This guide explains how to set up LinkedIn as an authentication provider with Hexclave. LinkedIn OAuth2 allows users to sign in to your application using their LinkedIn account. ## Integration Steps 1. Log in to the [LinkedIn Developer Portal](https://www.linkedin.com/developers/apps). 2. Click **Create app** to create a new application. 3. Enter your **App name** and select a **LinkedIn Page** to associate with your app (or create a new one). 4. Upload an **App logo** (required for production apps). 5. Enter the **App description** and your **Business email**. 6. Check the **Legal agreement** box and click **Create app**. 7. On your app's dashboard, click **Auth** tab from the left sidebar. 8. Under **OAuth 2.0 settings**, add the following redirect URL: `https://api.hexclave.com/api/v1/auth/oauth/callback/linkedin` 9. Under **Products**, request access to **Sign In with LinkedIn** by clicking **Request access**. Complete any required information. 10. Under **OAuth 2.0 scopes**, make sure at least the following scopes are selected: * `r_emailaddress` * `r_liteprofile` 11. Once approved, navigate to the **Auth** tab again to find your **Client ID** and **Client Secret**. 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **LinkedIn** as the provider. 3. Set the **Client ID** and **Client Secret** you obtained from the LinkedIn Developer Portal earlier. ### Need More Help? * Check the [LinkedIn OAuth2 Documentation](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow) * Join our [Discord](https://discord.hexclave.com) # Microsoft Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/microsoft Set up Microsoft as an authentication provider with Hexclave This guide explains how to set up Microsoft as an authentication provider with Hexclave. Microsoft OAuth allows users to sign in to your application using their Microsoft account. For Development purposes, Hexclave uses shared keys for this provider. Shared keys are automatically created by Hexclave, but show Hexclave's logo on the OAuth sign-in page. You should replace these before you go into production. ## Integration Steps 1. Navigate to the [Microsoft Entra admin center](https://entra.microsoft.com/) (formerly Azure AD). 2. In the left sidebar, go to **Applications** > **App registrations**. 3. Click **New registration** at the top of the page. 4. Enter a name for your application. 5. Under **Supported account types**, select the option that best suits your needs (typically **Accounts in any organizational directory and personal Microsoft accounts**). 6. In the **Redirect URI** section, select **Web** as the platform and enter `https://api.hexclave.com/api/v1/auth/oauth/callback/microsoft` 7. Click **Register** to create the application. 8. You'll be redirected to the app's Overview page. Note the **Application (client) ID** displayed at the top. 9. In the left sidebar, click **Certificates & secrets**. 10. Under **Client secrets**, click **New client secret**. 11. Add a description, select an expiration period, and click **Add**. 12. Copy the **Value** of the client secret immediately (you won't be able to see it again). 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **Microsoft** as the provider. 3. Set the **Client ID** (Application ID) and **Client Secret** you obtained from the Microsoft Entra admin center. ## Things to Know About Microsoft OAuth * **Emails are not marked as verified.** Microsoft doesn't attest that the user controls the email it returns, so Hexclave treats Microsoft emails as unverified. See Microsoft's [claims validation guidance](https://learn.microsoft.com/en-us/entra/identity-platform/claims-validation#validate-the-subject). * **Supported account types control who can sign in** (custom OAuth keys only). When using your own Microsoft OAuth app, you can set the tenant type in the Hexclave dashboard or config. The value maps to the `{tenant}` segment of Microsoft's authorize/token endpoints: `common` (work/school **and** personal accounts), `organizations` (work/school only), `consumers` (personal only, the default), or a specific tenant ID/domain. See [Microsoft's endpoint reference](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints). This setting does not apply to the shared development keys. ### Need More Help? * Check the [Microsoft identity platform Documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/) * Join our [Discord](https://discord.hexclave.com) # Passkey Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/passkey Set up passkey authentication with Hexclave using WebAuthn This guide explains how to set up Passkey authentication with Hexclave. Passkeys allow users to sign in to your application securely using biometrics, mobile devices, or security keys. Passkeys provide a more secure and convenient authentication method compared to traditional passwords by using WebAuthn standard. ## Integration Steps 1. Log in to the [Hexclave dashboard](https://app.hexclave.com/). 2. Select your project from the dashboard. 3. In the left sidebar, select **Auth Methods**. 4. Find the **Passkey** authentication method and toggle it to enable. 5. Save your changes. 1. Make sure you've installed the right Hexclave SDK package for your framework. For example, in Next.js: ```bash theme={null} npm install @hexclave/next ``` For other frameworks, use the package shown in [Setup](/guides/getting-started/setup), such as `@hexclave/react`, `@hexclave/js`, or `@hexclave/tanstack-start`. 2. Add Passkey support to your sign-in component by using the built-in Hexclave components or creating your own implementation with the SDK. Using built-in components: ```jsx theme={null} import { SignIn } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function SignInPage() { return ; } ``` The built-in components will automatically show the passkey option when it's enabled in your project. ## How Passkey Authentication Works 1. **Registration**: When a user creates a new passkey, their device generates a unique public-private key pair. The private key stays on the user's device, while the public key is sent to Hexclave's servers. 2. **Authentication**: When a user wants to sign in, Hexclave sends a challenge to the user's device. The device uses the private key to sign the challenge, and sends the signature back to Hexclave for verification. 3. **Cross-device authentication**: Users can create passkeys on one device and use them to sign in on another device using QR codes or nearby device detection. For the most up-to-date compatibility information, refer to the [WebAuthn browser compatibility chart](https://caniuse.com/webauthn). # Spotify Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/spotify Set up Spotify as an authentication provider with Hexclave This guide explains how to set up Spotify as an authentication provider with Hexclave. Spotify OAuth allows users to sign in to your application using their Spotify account. For Development purposes, Hexclave uses shared keys for this provider. Shared keys are automatically created by Stack, but show Stack's logo on the OAuth sign-in page. You should replace these before you go into production. ## Integration Steps 1. Navigate to the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/). 2. Log in with your Spotify account. 3. Click **Create app** to create a new application. 4. Enter an **App name** and **App description**. 5. Under **Redirect URI**, add `https://api.hexclave.com/api/v1/auth/oauth/callback/spotify` 6. Check the agreement checkbox and click **Create**. 7. You'll be redirected to your app's dashboard. Note your **Client ID** displayed on this page. 8. Click **Settings** to view more details about your app. 9. In the settings page, you can view your **Client Secret** by clicking **Show client secret**. 10. If needed, you can adjust the app settings, including adding additional redirect URIs. 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **Spotify** as the provider. 3. Set the **Client ID** and **Client Secret** you obtained from the Spotify Developer Dashboard earlier. ### Need More Help? * Check the [Spotify Web API Authorization Documentation](https://developer.spotify.com/documentation/general/guides/authorization/) * Join our [Discord](https://discord.hexclave.com) # Twitch Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/twitch Set up Twitch as an authentication provider with Hexclave This guide explains how to set up Twitch as an authentication provider with Hexclave. Twitch OAuth allows users to sign in to your application using their Twitch account. ## Integration Steps 1. Navigate to the [Twitch Developer Console](https://dev.twitch.tv/console). 2. Log in with your Twitch account. 3. Navigate to **Applications** and click **Register New Application**. 4. Enter a **Name** and select a **Category**. 5. Under **OAuth Redirect URLs**, add `https://api.hexclave.com/api/v1/auth/oauth/callback/twitch` 6. Click **Create**. 7. You'll be redirected to your app's dashboard. 8. Click **Manage** of the app you just created to view more details about your app. 9. Click "New Secret" to generate a new secret. 10. Copy and save the **Client ID** and **Client Secret**. 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **Twitch** as the provider. 3. Set the **Client ID** and **Client Secret** you obtained from the Twitch Developer Console earlier. ### Need More Help? * Check the [Twitch OAuth Documentation](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/) * Join our [Discord](https://discord.hexclave.com) # Two-Factor Authentication (2FA) Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/two-factor-auth Learn how Two-Factor Authentication works with Hexclave This guide explains how Two-Factor Authentication (2FA) works with Hexclave. 2FA adds an extra layer of security by requiring users to provide a verification code in addition to their password. Hexclave implements TOTP (Time-based One-Time Password) for two-factor authentication, which is compatible with standard authenticator apps like Google Authenticator, Microsoft Authenticator, and Authy. 2FA is enabled by default at the platform level and can be configured by individual users. ## Integration Steps 2FA is enabled by default on the Hexclave platform. Unlike other authentication methods, you don't need to enable it specifically for your project. To allow your users to set up 2FA for their accounts: 1. Make sure you've installed the right Hexclave SDK package for your framework. For example, in Next.js: ```bash theme={null} npm install @hexclave/next ``` For other frameworks, use the package shown in [Setup](/guides/getting-started/setup), such as `@hexclave/react`, `@hexclave/js`, or `@hexclave/tanstack-start`. 2. Use the Hexclave components to give users access to their account settings, where they can enable 2FA: ```jsx theme={null} import { AccountSettings } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function SettingsPage() { return ; } ``` 3. The built-in Hexclave components will handle the entire 2FA setup process, including QR code generation, verification, and recovery codes. ## How Hexclave 2FA Works Hexclave uses the industry-standard TOTP (Time-based One-Time Password) algorithm for two-factor authentication: 1. **User Setup**: When a user enables 2FA in their account settings, Hexclave generates a secret key that is shared with the user's authenticator app (usually via a QR code). 2. **Code Generation**: The authenticator app generates a 6-digit code that changes every 30 seconds, based on the shared secret and the current time. ## Recommended Authenticator Apps The following authenticator apps are compatible with Hexclave 2FA: * Google Authenticator (Android, iOS) * Microsoft Authenticator (Android, iOS) * Authy (Android, iOS, desktop) * 1Password (Android, iOS, desktop) * LastPass Authenticator (Android, iOS) # X (Twitter) Source: https://docs.hexclave.com/guides/apps/authentication/auth-providers/x-twitter Set up X (Twitter) as an authentication provider with Hexclave This guide explains how to set up X (formerly Twitter) as an authentication provider with Hexclave. X OAuth 2.0 allows users to sign in to your application using their X account. ## Integration Steps 1. Log in to the [X Developer Portal](https://developer.twitter.com/). 2. Navigate to the [Developer Portal Dashboard](https://developer.twitter.com/en/portal/dashboard). 3. Click on **+ Create Project** to create a new project. 4. Enter a name for your project and select **Web App, Automated App or Bot** as the use case, then click **Next**. 5. Enter a description for your project and click **Next**. 6. Name your app and click **Next**. 7. In the **App settings** section, find your API Key and Secret. These will serve as your OAuth 2.0 Client ID and Client Secret. 8. In the left sidebar, click on your project, then select the app you just created. 9. Click on the **Settings** tab and scroll to the **User authentication settings**. 10. Click **Set up** or **Edit** if already configured. 11. Enable **OAuth 2.0** and set the following details: * **Type of App**: Web App * **Callback URL / Redirect URL**: `https://api.hexclave.com/api/v1/auth/oauth/callback/x` * **Website URL**: Your website's URL 12. Under **App permissions**, select your scopes. 13. Click **Save** to apply your changes. 1. On the Hexclave dashboard, select **Auth Methods** in the left sidebar. 2. Click **Add SSO Providers** and select **X (Twitter)** as the provider. 3. Set the **Client ID** (your API Key) and **Client Secret** you obtained from the X Developer Portal earlier. ### Need More Help? * Check the [X OAuth 2.0 documentation](https://developer.twitter.com/en/docs/authentication/oauth-2-0) * Join our [Discord](https://discord.hexclave.com) # CLI App Authentication Source: https://docs.hexclave.com/guides/apps/authentication/cli-authentication How to authenticate users in your own command-line application using Hexclave If you're building your own command-line application, you can use Hexclave to let users log in from a terminal. This page is about adding authentication to your own CLI app. For the official Hexclave CLI, see the [Hexclave CLI guide](/guides/going-further/cli). To do so, we provide a Python template that you can use as a starting point. [Download it here](https://github.com/hexclave/hexclave/tree/main/docs/public/stack-auth-cli-template.py) and copy it into your project, for example: ``` └─ my-python-app ├─ main.py └─ stack_auth_cli_template.py # <- the file you just downloaded (rename to use underscores for Python import) ``` Then, you can import the `prompt_cli_login` function. The project ID is enough for most projects; only pass `publishable_client_key` if the project has `requirePublishableClientKey` enabled. ```py theme={null} from stack_auth_cli_template import prompt_cli_login # prompt the user to log in refresh_token = prompt_cli_login( app_url="https://your-app-url.example.com", project_id="your-project-id-here", ) if refresh_token is None: print("User cancelled the login process. Exiting") exit(1) # you can also store the refresh token in a file, and only prompt the user to log in if the file doesn't exist # you can now use the REST API with the refresh token def stack_auth_request(method, endpoint, **kwargs): # ... see the REST API overview for required Hexclave headers # https://docs.hexclave.com/api/overview def get_access_token(refresh_token): access_token_response = stack_auth_request( 'post', '/api/v1/auth/sessions/current/refresh', headers={ 'x-hexclave-refresh-token': refresh_token, } ) return access_token_response['access_token'] def get_user_object(access_token): return stack_auth_request( 'get', '/api/v1/users/me', headers={ 'x-hexclave-access-token': access_token, } ) user = get_user_object(get_access_token(refresh_token)) print("The user is logged in as", user['display_name'] or user['primary_email']) ``` # Connected Accounts Source: https://docs.hexclave.com/guides/apps/authentication/connected-accounts Managing third-party OAuth access tokens Stack has good support for working with OAuth and OIDC providers, such as Google, Facebook, Microsoft, and others. Beyond using OAuth for signing in, Stack can manage your users' access token so you can invoke APIs on their behalf. For example, you can use this to send emails with Gmail, access repositories on GitHub, or access files on OneDrive. A connected account is simply an external account that is linked to the user in some way. If you are not using shared keys (see note below), any user created with "Sign up with OAuth" is automatically connected to the account they signed up with, but it's also possible to connect a user with a provider that is unavailable for sign in. You cannot connect a user's accounts with shared OAuth keys. You need to set up your own OAuth client ID and client secret in Stack's dashboard. For more details, check [Going to Production](/guides/apps/launch-checklist/overview#oauth-providers). ## Connecting with OAuth providers You can access a user's connected account with the `user.getConnectedAccount(providerId)` function or `user.useConnectedAccount(providerId)` hook. Often, you'll want to redirect the user to the OAuth provider's authorization page if they have not connected the account yet. Just like the `getUser(...)` function, `getConnectedAccount(...)` can also take an `{ or: "redirect" }` argument to achieve this. Here's how to connect with Google: ```jsx theme={null} 'use client'; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function Page() { const user = useUser({ or: 'redirect' }); // Redirects to Google authorization if not already connected const account = user.useConnectedAccount('google', { or: 'redirect' }); // Account is always defined because of the redirect return
Google account connected
; } ``` ## Providing scopes Most providers have access control in the form of OAuth scopes. These are the permissions that the user will see on the authorization screen (eg. "Your App wants access to your calendar"). For instance, to read Google Drive content, you need the `https://www.googleapis.com/auth/drive.readonly` scope: ```jsx theme={null} 'use client'; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function Page() { const user = useUser({ or: 'redirect' }); // Redirects to the Google authorization page, requesting access to Google Drive const account = user.useConnectedAccount('google', { or: 'redirect', scopes: ['https://www.googleapis.com/auth/drive.readonly'] }); // Account is always defined because of the redirect return
Google Drive connected
; } ``` Check your provider's API documentation to find a list of available scopes. ## Retrieving the access token Once connected with an OAuth provider, obtain the access token with the `account.getAccessToken()` function. Check your provider's API documentation to understand how you can use this token to authorize the user in requests. ```jsx theme={null} 'use client'; import { useEffect, useState } from 'react'; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function Page() { const user = useUser({ or: 'redirect' }); const account = user.useConnectedAccount('google', { or: 'redirect', scopes: ['https://www.googleapis.com/auth/drive.readonly'] }); const { accessToken } = account.useAccessToken(); const [response, setResponse] = useState(); useEffect(() => { fetch('https://www.googleapis.com/drive/v3/files', { headers: { Authorization: `Bearer ${accessToken}` } }) .then((res) => res.json()) .then((data) => setResponse(data)) .catch((err) => console.error(err)); }, [accessToken]); return
{response ? JSON.stringify(response) : 'Loading...'}
; } ``` ## Sign-in default scopes To avoid showing the authorization page twice, you can already request scopes during the sign-in flow. This approach is optional. Some applications may prefer to request extra permissions only when needed, while others might want to obtain all necessary permissions upfront. To do this, edit the `oauthScopesOnSignIn` setting of your `hexclaveServerApp`: ```jsx title="stack/server.ts" theme={null} export const hexclaveServerApp = new HexclaveServerApp({ // ...your other settings... oauthScopesOnSignIn: { google: ['https://www.googleapis.com/auth/drive.readonly'] } }); ``` ## OAuth account merging strategies When a user attempts to sign in with an OAuth provider that matches an existing account, Stack provides different strategies for handling the authentication flow. The available strategies are: * Allow duplicates (legacy default) * Link method (new default) * Block duplicates (most secure) The "Link" strategy is the default behavior. If a user attempts to sign in with an OAuth provider that matches an existing account, Stack will link the OAuth identity to the existing account, and the user will be signed into that account. This requires both of the credentials to be verified, or otherwise the link will be blocked, in the same way as the "Block" strategy. The "Allow" strategy is the default behavior for old projects. If a user attempts to sign in with an OAuth provider that has an existing account with the same email address, Stack will create a separate account for the user. The "Block" strategy will explicitly raise an error if a user attempts to sign in with an OAuth provider that matches an existing account. # JWT Tokens Source: https://docs.hexclave.com/guides/apps/authentication/jwts Understand how Hexclave uses JSON Web Tokens for authentication JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. Hexclave uses JWTs for secure authentication and authorization. You don't need to worry about JWTs if you're using Hexclave. However, if you are an expert user and want the full flexibility to manually verify JWTs for performance or other reasons, this page is for you. ## What is a JWT? A JWT is a string that consists of three parts separated by dots (`.`): 1. **Header**: Contains metadata about the token, such as the signing algorithm 2. **Payload**: Contains the claims (data) about the user or entity 3. **Signature**: Used to verify the token's authenticity The structure looks like this: `header.payload.signature` ## Hexclave JWT Structure Hexclave JWTs contain standardized headers and claims that power authentication throughout the platform. ### Header * **`alg`**: Always `ES256` * **`kid`**: Identifies which public key from the JWKS should be used for verification ### Standard Claims * **`iss` (Issuer)**: `https://api.hexclave.com/api/v1/projects/` for regular users, or `https://api.hexclave.com/api/v1/projects-anonymous-users/` for anonymous sessions * **`sub` (Subject)**: The user ID this token represents * **`aud` (Audience)**: The intended recipient of the token — `` for regular sessions, `:anon` for anonymous sessions * **`exp` (Expiration)**: When the token expires (Unix timestamp) * **`iat` (Issued At)**: When the token was issued (Unix timestamp) ### Hexclave Specific Claims * **`project_id`**: Your Hexclave project ID * **`branch_id`**: The project branch (currently always `main`) * **`refresh_token_id`**: ID of the associated refresh token * **`role`**: Always set to `authenticated` for valid users * **`name`**: The user's display name (nullable) * **`email`**: The user's primary email address (nullable) * **`email_verified`**: Whether the user's email has been verified * **`selected_team_id`**: The currently selected team ID (nullable) * **`is_anonymous`**: Whether this is an anonymous user session * **`is_restricted`**: Whether the user is restricted (e.g., unverified email, anonymous, or restricted by an administrator) * **`restricted_reason`**: Why the user is restricted (nullable). The `type` field is `anonymous`, `email_not_verified`, or `restricted_by_administrator` ## Example JWT Payload Here's what a typical Hexclave JWT payload looks like: ```json theme={null} { "iss": "https://api.hexclave.com/api/v1/projects/project_abcdef", "sub": "user_123456", "aud": "project_abcdef", "exp": 1735689600, "iat": 1735603200, "project_id": "project_abcdef", "branch_id": "main", "refresh_token_id": "refresh_xyz789", "requires_totp_mfa": false, "role": "authenticated", "name": "John Doe", "email": "john@example.com", "email_verified": true, "selected_team_id": "team_789", "is_anonymous": false, "is_restricted": false, "restricted_reason": null } ``` Anonymous user tokens have the same shape, but: * `iss` becomes `https://api.hexclave.com/api/v1/projects-anonymous-users/` * `aud` becomes `:anon` * `is_anonymous` is `true` * `is_restricted` is `true` * `restricted_reason` is `{ "type": "anonymous" }` Restricted user tokens (e.g., users who haven't verified their email when verification is required) have: * `iss` becomes `https://api.hexclave.com/api/v1/projects-restricted-users/` * `aud` becomes `:restricted` * `is_restricted` is `true` * `restricted_reason` is `{ "type": "email_not_verified" }` Users restricted by an administrator (e.g., via [sign-up rules](/guides/apps/authentication/sign-up-rules)) have the same structure: * `iss` becomes `https://api.hexclave.com/api/v1/projects-restricted-users/` * `aud` becomes `:restricted` * `is_restricted` is `true` * `restricted_reason` is `{ "type": "restricted_by_administrator" }` ## Working with JWTs ### Client-Side Usage Hexclave automatically handles JWT tokens for you. When you use hooks like `useUser()`, the JWT is automatically included in API requests: **Next.js:** ```tsx theme={null} import { useUser } from '@hexclave/next'; export function UserProfile() { const user = useUser(); if (!user) { return
Please sign in
; } return
Welcome, {user.displayName}!
; } ``` **React:** ```tsx theme={null} import { useUser } from '@hexclave/react'; export function UserProfile() { const user = useUser(); if (!user) { return
Please sign in
; } return
Welcome, {user.displayName}!
; } ``` ### Server-Side Usage On the server side, you can access the JWT and its claims through the Hexclave API: ```typescript theme={null} import { hexclaveServerApp } from '@/stack'; export async function GET() { const user = await hexclaveServerApp.getUser(); if (!user) { return new Response('Unauthorized', { status: 401 }); } // Access user information from the JWT return Response.json({ id: user.id, displayName: user.displayName, primaryEmail: user.primaryEmail, selectedTeamId: user.selectedTeamId, // Other user properties... }); } ``` ### Manual JWT Verification If you need to manually verify a JWT (for example, in a different service), fetch the public keys from Hexclave's JWKS endpoint. Keys are derived per audience so the `kid` in the JWT header always matches one of the published keys. ```typescript theme={null} import * as jose from 'jose'; // Get the public key set from Hexclave const jwks = jose.createRemoteJWKSet( new URL('https://api.hexclave.com/api/v1/projects/YOUR_PROJECT_ID/.well-known/jwks.json') ); // Verify a regular (non-anonymous) access token try { const { payload } = await jose.jwtVerify(token, jwks, { issuer: 'https://api.hexclave.com/api/v1/projects/YOUR_PROJECT_ID', audience: 'YOUR_PROJECT_ID', }); console.log('JWT is valid:', payload); } catch (error) { console.error('JWT verification failed:', error); } ``` To support anonymous sessions, include those keys and allow both issuers and audiences: ```typescript theme={null} import * as jose from 'jose'; const jwks = jose.createRemoteJWKSet( new URL('https://api.hexclave.com/api/v1/projects/YOUR_PROJECT_ID/.well-known/jwks.json?include_anonymous=true') ); const { payload } = await jose.jwtVerify(token, jwks, { issuer: [ 'https://api.hexclave.com/api/v1/projects/YOUR_PROJECT_ID', 'https://api.hexclave.com/api/v1/projects-anonymous-users/YOUR_PROJECT_ID', ], audience: ['YOUR_PROJECT_ID', 'YOUR_PROJECT_ID:anon'], }); ``` To support restricted users (e.g., users who haven't verified their email), add `include_restricted=true`: ```typescript theme={null} import * as jose from 'jose'; const jwks = jose.createRemoteJWKSet( new URL('https://api.hexclave.com/api/v1/projects/YOUR_PROJECT_ID/.well-known/jwks.json?include_anonymous=true&include_restricted=true') ); // All three user types have different issuers const { payload } = await jose.jwtVerify(token, jwks, { issuer: [ 'https://api.hexclave.com/api/v1/projects/YOUR_PROJECT_ID', 'https://api.hexclave.com/api/v1/projects-anonymous-users/YOUR_PROJECT_ID', 'https://api.hexclave.com/api/v1/projects-restricted-users/YOUR_PROJECT_ID', ], audience: ['YOUR_PROJECT_ID', 'YOUR_PROJECT_ID:anon', 'YOUR_PROJECT_ID:restricted'], }); ``` ### Signing Keys * Private keys are deterministically derived from your project ID, optional anonymous audience, and the `STACK_SERVER_SECRET` environment variable. This means no key material is ever stored in the database. * The JWKS currently exposes both the latest key pair and a legacy compatibility key. Verification libraries automatically pick the correct key by matching the `kid` provided in the JWT header. * Tokens are always signed server-side; client SDKs never receive the private keys. ## Security Considerations ### Token Storage * **Never store JWTs in localStorage** for sensitive applications * Use secure, httpOnly cookies when possible * Hexclave handles secure token storage automatically ### Token Expiration * JWTs have a limited lifetime (default is 10 minutes via `STACK_ACCESS_TOKEN_EXPIRATION_TIME`) * Hexclave automatically refreshes tokens before they expire * Always check the `exp` claim when manually handling JWTs ### Signature Verification * Always verify JWT signatures using the public key * Never trust the contents of a JWT without verification * Hexclave SDKs handle verification automatically ## Troubleshooting ### Common Issues 1. **"JWT is expired"**: The token has passed its expiration time. Hexclave will automatically refresh it. 2. **"Invalid signature"**: The token was tampered with or signed with a different key. 3. **"Invalid audience"**: The token was issued for a different project or environment. ### Debugging JWTs Use a JWT viewer such as [jwt.io](https://jwt.io/) to inspect tokens and verify their contents. Pay special attention to: * Expiration times (`exp` claim) * Audience (`aud` claim) matching your project * Required claims are present ## Best Practices 1. **Let Hexclave handle tokens**: Use the provided SDKs instead of manual JWT handling 2. **Validate on the server**: Always verify JWTs on your backend 3. **Check expiration**: Ensure tokens haven't expired before using them 4. **Use HTTPS**: Always transmit JWTs over secure connections 5. **Monitor token usage**: Log authentication events for security monitoring ## Related Concepts * [API Keys](/guides/apps/api-keys/overview) - Alternative authentication method for server-to-server communication * [Setup](/guides/getting-started/setup) - How to verify user sessions in your backend * [Permissions](/guides/apps/rbac/overview) - Understanding user permissions (not included in JWTs) * [Teams](/guides/apps/teams/overview) - Understanding team context in JWTs # Authentication Source: https://docs.hexclave.com/guides/apps/authentication/overview Integrate Hexclave with AI-powered tools and services TODO stub # Restricted Users Source: https://docs.hexclave.com/guides/apps/authentication/restricted-users Understand and handle users with limited access Restricted users are signed-in users whose account exists, but has not been granted normal application access yet. Stack marks these users with `user.isRestricted === true` and provides a `user.restrictedReason` explaining why. By default, Hexclave treats restricted users like unauthenticated users in most SDK calls. This prevents accounts that still need verification, review, or conversion from accidentally getting access to protected product flows. ## When users are restricted Users can be restricted for a few reasons: * **Email not verified**: the project requires email verification before full access. * **Anonymous user**: anonymous users can interact with the app, but are always restricted until converted. * **Restricted by administrator**: the user was restricted manually or by a [sign-up rule](/guides/apps/authentication/sign-up-rules). You can inspect the reason from the SDK: ```ts my-app.ts theme={null} import { hexclaveServerApp } from "../src/stack/server"; const user = await hexclaveServerApp.getUser({ includeRestricted: true }); if (user?.isRestricted) { console.log(user.restrictedReason?.type); } ``` The current `restrictedReason.type` values are: | Type | Meaning | | ----------------------------- | ------------------------------------------------------ | | `email_not_verified` | The user still needs to verify their email address. | | `anonymous` | The user is an anonymous user. | | `restricted_by_administrator` | The user was restricted manually or by a sign-up rule. | ## Loading restricted users Most calls exclude restricted users unless you explicitly opt in. Use `includeRestricted: true` when you are building onboarding, email verification, account review, or anonymous-user conversion flows. ```ts my-app.ts theme={null} import { hexclaveServerApp } from "../src/stack/server"; const user = await hexclaveServerApp.getUser({ includeRestricted: true }); if (user?.isRestricted) { console.log("Needs onboarding:", user.restrictedReason?.type); } ``` ```tsx my-react-component.tsx theme={null} "use client"; import { hexclaveClientApp } from "../src/stack/client"; export default function OnboardingGate() { const user = hexclaveClientApp.useUser({ includeRestricted: true }); if (!user) { return Sign in; } if (user.isRestricted) { return ; } return
Welcome back, {user.displayName ?? user.primaryEmail}
; } ```
Anonymous users are restricted by definition. Passing `{ or: "anonymous" }` automatically includes restricted users, and cannot be combined with `{ includeRestricted: false }`. ## Handling restricted users Treat restricted users as a separate state from both "signed out" and "fully signed in". A good default is to show a page that tells the user what they need to do next. ```tsx restricted-user-message.tsx theme={null} function RestrictedUserMessage({ reason }: { reason: string | undefined }) { if (reason === "email_not_verified") { return
Please verify your email address to continue.
; } if (reason === "anonymous") { return Create an account to save your progress.; } return
Your account is waiting for review.
; } ``` For API routes or backend actions, keep using the default behavior unless the endpoint is specifically meant to serve restricted users. This helps prevent partially onboarded accounts from reaching product APIs. ## Restricted users in JWTs Restricted users receive tokens with `is_restricted` and `restricted_reason` claims. If your backend verifies Hexclave JWTs directly, make sure you reject restricted users unless the endpoint intentionally supports them. When fetching Hexclave's JWKS, restricted-user signing keys are excluded by default. Include them only for services that intentionally accept restricted users: ```txt jwks-url.txt theme={null} /.well-known/jwks.json?include_restricted=true ``` If you also accept anonymous users, use `include_anonymous=true`; anonymous keys imply restricted-user keys. ## Admin and sign-up review Sign-up rules can restrict a user instead of rejecting the sign-up outright. This is useful when you want the account to exist, but need manual review before granting access. For more details on creating rules that restrict users, see [Sign-up Rules](/guides/apps/authentication/sign-up-rules). # Sign-up Rules Source: https://docs.hexclave.com/guides/apps/authentication/sign-up-rules Control who can sign up for your application with customizable rules. Sign-up rules let you control who can sign up for your application. You can create rules that evaluate sign-up attempts based on conditions like email domain or authentication method, then allow, reject, or restrict users accordingly. Rules are evaluated during sign-up for all authentication methods (password, magic link, OAuth, passkey). When a user attempts to sign up, Stack evaluates each rule in priority order—the first matching rule determines the outcome. If no rules match, the default action is used. ## Creating rules Navigate to **Sign-up Rules** in your project dashboard to create and manage rules. To add a rule: 1. Click **Add Rule** 2. Enter a name for your rule (e.g., "Block disposable emails") 3. Configure the conditions using the visual builder 4. Select an action (Allow, Reject, Restrict, or Log) 5. Click **Create rule** ### Available conditions When building rule conditions, you have access to these context variables: | Variable | Type | Description | | --------------- | ------ | --------------------------------------------------------------------------------------- | | `email` | string | The user's email address (normalized to lowercase) | | `emailDomain` | string | The domain part of the email (after @) | | `authMethod` | string | The authentication method: `password`, `otp`, `oauth`, or `passkey` | | `oauthProvider` | string | The OAuth provider ID if using OAuth (e.g., `google`, `github`), empty string otherwise | The condition builder supports these operations on string values: * `contains("substring")` - Check if value contains a substring * `startsWith("prefix")` - Check if value starts with a prefix * `endsWith("suffix")` - Check if value ends with a suffix * `matches("regex")` - Check if value matches a regular expression * `==` and `!=` - Exact equality comparisons You can combine multiple conditions using AND/OR logic. ## Actions ### Allow The user signs up normally. Use this to explicitly allow certain users when your default action is set to reject. ### Reject Blocks the sign-up and shows the user: "Your sign up was rejected by an administrator's sign-up rule." You can optionally add an internal message for logging (not shown to users). ### Restrict The user signs up, but their account is marked as restricted. Restricted users have limited access and can be reviewed by admins before gaining full access. See [JWT Tokens](/guides/apps/authentication/jwts) for how restricted status appears in tokens. ### Log The rule is triggered and logged for analytics, but no action is taken. Use this to monitor patterns before implementing blocking rules. ## Priority and default action Rules are evaluated in priority order (highest first). You can reorder rules by dragging them in the dashboard. Only the first matching rule's action is applied, so place your allow rules before reject rules if you want to allow specific users while blocking others. The default action applies when no rules match: * **Allow** (default): Sign-ups are allowed unless a rule explicitly rejects them * **Reject**: Sign-ups are blocked unless a rule explicitly allows them Set the default to "Reject" when you want to only allow sign-ups from specific domains. ## Common use cases ### Block disposable email domains Block users signing up with temporary email addresses: * Condition: `emailDomain.matches("(tempmail|throwaway|guerrillamail)\\..*")` * Action: Reject ### Allow only corporate domains 1. Set default action to **Reject** 2. Create an allow rule with condition: `emailDomain == "company1.com" || emailDomain == "company2.com"` ### Restrict non-verified auth methods Require manual review for users who sign up without email verification: * Condition: `authMethod == "oauth" && oauthProvider != "google"` * Action: Restrict ### Different rules for different auth methods Allow password sign-ups from any domain, but restrict OAuth sign-ups: 1. Rule 1: `authMethod == "password"` → Allow 2. Rule 2: `authMethod == "oauth"` → Restrict 3. Default: Allow ## Analytics The dashboard shows analytics for each rule, including how many times it's been triggered over the past 48 hours. Use this to understand your sign-up patterns and tune your rules. ## Testing rules You can test your sign-up rules using the built-in rule tester. It simulates sign-up requests and shows which rules would trigger and what the outcome would be—without affecting real users. To open the tester, scroll to the bottom of the Sign-up Rules page and click **Open tester**. ### Test inputs Enter the following to simulate a sign-up attempt: * **Email**: The email address to test (e.g., `user@company.com`) * **Auth method**: The authentication method (`Password`, `OTP`, `OAuth`, or `Passkey`) * **OAuth provider**: The OAuth provider ID (only used when auth method is OAuth) Click **Run test** to see the results. ### Understanding the results The tester displays: * **Outcome**: Whether the sign-up would be allowed or rejected, and whether the decision came from a specific rule or the default action. * **Triggered rules**: All rules that matched the test input, showing each rule's name, condition, action type, and whether it was the deciding rule. * **Evaluation trace**: A detailed view of how every rule was evaluated—which matched, which didn't, which were disabled, and any errors. * **Normalized context**: How the test input was parsed, including the extracted email domain. Useful for debugging conditions that reference `email`, `emailDomain`, `authMethod`, or `oauthProvider`. # User Onboarding Source: https://docs.hexclave.com/guides/apps/authentication/user-onboarding Implementing a user onboarding page and collecting information on sign-up Sometimes, you may want to collect additional information from users during sign-up, for example a real name or address. The most straightforward approach is to redirect users to an onboarding page right after they sign up. However, this is not recommended for the following reasons: 1. Users can accidentally (or purposefully) close or navigate away from the page before completing the onboarding. 2. Redirect URLs may vary depending on the context. For instance, if a user is redirected to a sign-in page after trying to access a protected page, they'll expect to return to the original protected page post-authentication. Instead, a more reliable strategy is to store an `onboarded` flag in the user's metadata and redirect users to the onboarding page if they haven't completed it yet. ## Example implementation Let's say you have an onboarding page that asks for an address and stores it in the user's [metadata](/guides/getting-started/user-fundamentals#custom-metadata): ```jsx theme={null} export default function OnboardingPage() { const user = useUser(); const router = useRouter(); const [address, setAddress] = useState(''); return <> setAddress(e.target.value)} /> ); } ``` While the above implementation offers a basic onboarding process, users can still skip onboarding by directly sending an API request to update the `clientMetadata.onboarded` flag. If you want to ensure that onboarding cannot be bypassed on the API level, you should create a server endpoint to validate and store the data, then save the `onboarded` flag in the `clientReadOnlyMetadata` on the server side after validation. Next, we can create a hook/function to check if the user has completed onboarding and redirect them to the onboarding page: ```jsx theme={null} 'use client'; import { useEffect } from 'react'; import { useUser } from '@hexclave/next'; // replace `next` with the correct framework SDK package import { useRouter } from 'next/navigation'; export function useOnboarding() { const user = useUser(); const router = useRouter(); useEffect(() => { if (!user.clientReadOnlyMetadata.onboarded) { router.push('/onboarding'); } }, [user]); } ``` ```jsx theme={null} import { hexclaveServerApp } from '@/stack/server'; import { redirect } from 'next/navigation'; export async function ensureOnboarded() { const user = await hexclaveServerApp.getUser(); if (!user.clientReadOnlyMetadata.onboarded) { redirect('/onboarding'); } } ``` You can then use these functions wherever onboarding is required: ```jsx theme={null} import { useOnboarding } from '@/app/onboarding-hooks'; import { useUser } from '@hexclave/next'; // replace `next` with the correct framework SDK package export default function HomePage() { useOnboarding(); const user = useUser(); return (
Welcome to the app, {user.displayName}
); } ```
```jsx theme={null} import { ensureOnboarding } from '@/app/onboarding-functions'; import { hexclaveServerApp } from '@/stack/server'; export default async function HomePage() { await ensureOnboarding(); const user = await hexclaveServerApp.getUser(); return (
Welcome to the app, {user.displayName}
); } ```
# Data Vault Source: https://docs.hexclave.com/guides/apps/data-vault/overview An encrypted key-value store for sensitive data, with zero-knowledge security Data Vault is an encrypted key-value store built into Hexclave. It lets you securely store sensitive data — API tokens, connection strings, secrets, or any other values — without ever exposing plaintext to Hexclave's database or operators. ## How it works Data Vault uses a **double encryption** design: 1. **Client-side encryption** — Your SDK encrypts values and hashes keys locally before they leave your server, using a secret that only you know. Hexclave never sees your plaintext keys or values. 2. **Server-side encryption** — Hexclave adds a second layer of envelope encryption using a rotating master key, so even the encrypted data at rest is further protected. Because keys are hashed before storage, **you cannot list or enumerate keys** in a store. You must know the exact key to retrieve a value. If you lose your secret, your data is unrecoverable. Even Hexclave cannot decrypt your values without it. Keep your secret safe and backed up. ## Setup ### 1. Create a store Go to your project's **Data Vault** page in the [Hexclave dashboard](https://app.hexclave.com) and create a new store. Each store has a unique ID that you'll reference in your code. ### 2. Generate a secret Your secret can be any string, but for strong security it should be at least 32 characters long and provide 256 bits of entropy. Store it as an environment variable: ```bash title=".env" theme={null} STACK_DATA_VAULT_SECRET=your-randomly-generated-secret-here ``` ### 3. Use the SDK Data Vault is accessed through the **server app** only — it requires your secret server key. ```typescript title="server-example.ts" theme={null} const store = await hexclaveServerApp.getDataVaultStore("my-store-id"); const key = user.id; // Store a value await store.setValue(key, "my-sensitive-value", { secret: process.env.STACK_DATA_VAULT_SECRET, }); // Retrieve a value const value = await store.getValue(key, { secret: process.env.STACK_DATA_VAULT_SECRET, }); // value is the decrypted string, or null if the key doesn't exist ``` ## API reference ### `getDataVaultStore(id)` Returns a `DataVaultStore` object for the given store ID. The store must already exist in your project config (created via the dashboard). ```typescript theme={null} const store = await hexclaveServerApp.getDataVaultStore("my-store-id"); ``` ### `store.getValue(key, { secret })` Retrieves the decrypted value for the given key, or `null` if the key doesn't exist. ```typescript theme={null} const value = await store.getValue("some-key", { secret: process.env.STACK_DATA_VAULT_SECRET, }); ``` ### `store.setValue(key, value, { secret })` Stores an encrypted value for the given key. If the key already exists, it is overwritten. ```typescript theme={null} await store.setValue("some-key", "some-value", { secret: process.env.STACK_DATA_VAULT_SECRET, }); ``` ## Security model * **Keys** are hashed with an iterated hash (100,000 iterations) derived from your secret and the logical key. The server only stores the hash. * **Values** are encrypted client-side using a derived key from the same secret + key pair, then re-encrypted server-side with KMS envelope encryption. * **Your secret** never leaves your server. Hexclave's API only receives hashed keys and double-encrypted values. * **No enumeration** — since only hashed keys are stored, there is no way to list all keys in a store. This is a deliberate security property. ## Use cases * **Storing third-party API tokens** — safely persist user-specific tokens for external services * **Connection strings** — store database or service connection strings per-tenant * **Encryption keys** — use Data Vault as a key store for your own application-level encryption * **Any sensitive per-user data** — anything you don't want in plaintext metadata fields # Emails Source: https://docs.hexclave.com/guides/apps/emails/overview Send custom emails, manage templates, and track delivery - all from Hexclave. Hexclave includes a full email system for sending transactional and marketing emails to your users. It handles rendering, delivery, scheduling, notification preferences, and tracking out of the box. ## Email types There are two categories of email: * **Transactional** - Required for your app to function (verification, password reset, receipts). Users cannot opt out. * **Marketing** - Promotional or informational. Always includes an unsubscribe link. Users can opt out. Never send marketing content as transactional emails. Doing so can get your domain blacklisted by spam filters. ## Sending emails Emails are sent from your server using `hexclaveServerApp.sendEmail()`. You must provide the content (HTML, a template, or a draft) and the recipients. ### Send to specific users ```typescript theme={null} await hexclaveServerApp.sendEmail({ userIds: ['user-id-1', 'user-id-2'], subject: 'Welcome to our platform!', html: '

Welcome!

Thanks for joining us.

', }); ``` ### Send to all users ```typescript theme={null} await hexclaveServerApp.sendEmail({ allUsers: true, templateId: 'your-template-id', subject: 'We just shipped a big update', variables: { featureName: 'Dark mode', }, }); ``` ### Send from a dashboard draft If you've composed an email in the dashboard's draft editor, you can trigger it programmatically: ```typescript theme={null} await hexclaveServerApp.sendEmail({ userIds: ['user-id'], draftId: 'your-draft-id', }); ``` ### Full options ```typescript theme={null} await hexclaveServerApp.sendEmail({ // Recipients - exactly one of these is required: userIds: ['user-id-1'], // specific users // allUsers: true, // or all users in your project // Content - exactly one of these is required: html: '

Hello!

', // raw HTML // templateId: 'template-id', // or a template with variables // draftId: 'draft-id', // or a dashboard draft // Optional: subject: 'Hello!', variables: { key: 'value' }, themeId: 'theme-id', // apply a specific theme // themeId: null, // use the default theme // themeId: false, // send with no theme at all notificationCategoryName: 'Marketing', scheduledAt: new Date('2025-12-25T00:00:00Z'), }); ``` ### `SendEmailOptions` type shape ```typescript theme={null} type SendEmailOptions = { userIds: string[]; // users to send to themeId?: string | null | false; // theme override subject?: string; // subject line notificationCategoryName?: string; // preference category html?: string; // raw HTML body templateId?: string; // template ID variables?: Record; // template variables }; ``` `sendEmail` requires a custom email server (SMTP, Resend, or Managed). It cannot be used with the shared development server. ### Error handling `sendEmail` returns a result object. Handle failures explicitly: ```typescript theme={null} const result = await hexclaveServerApp.sendEmail({ userIds: ['user-id'], html: '

Hello!

', subject: 'Test Email', }); if (result.status === 'error') { switch (result.error.code) { case 'REQUIRES_CUSTOM_EMAIL_SERVER': console.error('Please configure a custom email server'); break; case 'SCHEMA_ERROR': console.error('Invalid email data provided'); break; case 'USER_ID_DOES_NOT_EXIST': console.error('One or more user IDs do not exist'); break; } } ``` ## Scheduling Pass a `scheduledAt` date to delay delivery. The email enters the pipeline immediately but won't be sent until the scheduled time. ```typescript theme={null} await hexclaveServerApp.sendEmail({ userIds: ['user-id'], html: '

Happy New Year!

', subject: 'Happy New Year!', scheduledAt: new Date('2026-01-01T00:00:00Z'), }); ``` If `scheduledAt` is omitted, the email is sent as soon as possible. ## Email pipeline Emails are processed asynchronously through a multi-stage pipeline: 1. **Enqueue** - The email is saved to the outbox with its template, recipients, and scheduling metadata. 2. **Render** - The template TSX is compiled into HTML, subject, and plain text. 3. **Queue** - Rendered emails whose scheduled time has passed are queued for delivery, respecting your project's sending capacity. 4. **Send** - Emails are delivered, honoring notification preferences and skipping users who have unsubscribed. 5. **Track** - Delivery events (sent, opened, clicked, bounced, marked as spam) are recorded. You can monitor every email's status in the dashboard under **Emails → Sent**. ## Templates Templates are React Email components written in TSX. Each template receives the current `user`, `project`, and any custom `variables` you pass when sending. ```tsx theme={null} import { type } from "arktype"; import { Container } from "@react-email/components"; import { Subject, NotificationCategory, Props } from "@hexclave/emails"; export const variablesSchema = type({ featureName: "string", }); export function EmailTemplate({ user, project, variables, }: Props) { return (

Hi {user.displayName}, check out {variables.featureName}!

); } EmailTemplate.PreviewVariables = { featureName: "Dark mode", } satisfies typeof variablesSchema.infer; ``` Key concepts: * **`variablesSchema`** - Define the shape of your template variables using [arktype](https://arktype.io). Hexclave validates variables against this schema at render time. * **``** - Sets the email subject line from inside the template. * **``** - Declares whether this is a `"Transactional"` or `"Marketing"` email. * **`PreviewVariables`** - Sample data used for the live preview in the dashboard editor. ### Built-in templates Hexclave ships with templates for common auth flows. These are used automatically by the built-in authentication components: | Template | Trigger | | ---------------------- | -------------------------------------------------- | | **Email Verification** | User signs up or changes their email | | **Password Reset** | User requests a password reset | | **Magic Link / OTP** | User signs in with magic link or one-time password | | **Team Invitation** | User is invited to join a team | | **Sign-in Invitation** | User is invited to create an account | | **Payment Receipt** | A payment succeeds (one-time or subscription) | | **Payment Failed** | A payment fails | You can customize any built-in template from the dashboard under **Emails → Templates**. ## Themes Themes wrap your email content in a consistent layout - header, footer, background, branding. Hexclave includes three built-in themes: * **Default Light** - Clean white background with subtle shadow * **Default Dark** - Dark background with light text * **Default Colorful** - Light purple background with an accent border You can create custom themes in the dashboard under **Emails → Email Settings → Themes**. Themes are also TSX components: ```tsx theme={null} import { Html, Head, Tailwind, Body, Container } from "@react-email/components"; import { ThemeProps, ProjectLogo } from "@hexclave/emails"; export function EmailTheme({ children, unsubscribeLink, projectLogos }: ThemeProps) { return ( {children} {unsubscribeLink && (

Unsubscribe

)}
); } ``` Set a default theme for your project in the dashboard. You can also override the theme per-email with the `themeId` option, or pass `themeId: false` to send without any theme. ## Notification preferences Emails are categorized as either **Transactional** or **Marketing**. Users can opt out of Marketing emails but not Transactional ones. When sending, specify the category: ```typescript theme={null} await hexclaveServerApp.sendEmail({ userIds: ['user-id'], html: '

Check out our new feature!

', subject: 'Product Updates', notificationCategoryName: 'Marketing', }); ``` If a user has unsubscribed from Marketing emails, the email will be automatically skipped during delivery. Marketing emails always include an unsubscribe link. ## React components integration Emails integrate with Hexclave UI components automatically (for example verification, password reset, and magic-link flows).\ For custom flows, trigger `sendEmail` from your server code: ```typescript theme={null} import { hexclaveServerApp } from '@hexclave/next'; // replace `next` with the correct framework SDK package export async function inviteUser(userId: string) { const result = await hexclaveServerApp.sendEmail({ userIds: [userId], templateId: 'invitation-template', subject: "You're invited!", variables: { inviteUrl: 'https://yourapp.com/invite/token123', }, }); return result; } ``` ## Email server configuration Configure your email server in the dashboard under **Emails → Email Settings**. There are four options: ### Shared (development only) The default for new projects. Emails are sent from `noreply@sent-with-hexclave.com` using Hexclave's shared infrastructure. Good for development - not suitable for production. ### Custom SMTP Connect any SMTP provider. Configure: * **Host** - e.g. `smtp.sendgrid.net` * **Port** - typically 587 (STARTTLS) or 465 (implicit TLS) * **Username** and **Password** * **Sender email** and **Sender name** ### Resend Connect your [Resend](https://resend.com) account by entering your API key. Hexclave configures the SMTP connection automatically. ### Managed Let Hexclave manage your email domain. Hexclave handles DNS configuration and deliverability for you. Set up requires: 1. Choose a subdomain (e.g. `mail.yourapp.com`) 2. Add the DNS records Hexclave provides 3. Verify the domain in the dashboard The dashboard tests your email configuration automatically when you save it by sending a test email. ## Delivery stats Hexclave tracks delivery metrics across multiple time windows (hour, day, week, month): * **Sent** - Successfully delivered * **Bounced** - Rejected by the recipient's mail server * **Marked as spam** - Recipient flagged the email Access these programmatically: ```typescript theme={null} const info = await hexclaveServerApp.getEmailDeliveryStats(); // info.stats.day.sent, info.stats.day.bounced, etc. // info.capacity.rate_per_second, info.capacity.is_boost_active, etc. ``` Delivery capacity is managed automatically based on your sending reputation. If you need to temporarily increase throughput, you can activate a capacity boost: ```typescript theme={null} await hexclaveServerApp.activateEmailCapacityBoost(); ``` ## Drafts The dashboard includes a full draft editor where you can compose emails visually before sending. Drafts support: * TSX source editing with live preview * Theme selection * Recipient picker (specific users or all users) * Scheduling * Send history per draft Once a draft is sent, it's marked as sent and its delivery can be tracked in the outbox. # Fraud Protection Source: https://docs.hexclave.com/guides/apps/fraud-protection/overview Protect your project from fraud and abuse Fraud Protection helps you block abusive sign-ups and enforce safer onboarding rules. Use the Authentication app docs below for the implementation details currently available: * [Sign-up Rules](../authentication/sign-up-rules) * [Authentication Overview](../authentication/overview) # Launch Checklist Source: https://docs.hexclave.com/guides/apps/launch-checklist/overview Steps to prepare Stack for production use Stack makes development easy with various default settings, but these settings need to be optimized for security and user experience when moving to production. Here's a checklist of things you need to do before switching to production mode: ## Domains By default, Stack allows all localhost paths as valid callback URLs. This is convenient for development but poses a security risk in production because attackers could use their own domains as callback URLs to intercept sensitive information. Therefore, in production, Stack must know your domain (e.g., `https://your-website.com`) and only allow callbacks from those domains. Follow these steps when you're ready to push your application to production: Navigate to the `Domain & Handlers` tab in the Stack dashboard. If you haven't configured your handler, you can leave it as the default. (Learn more about handlers [here](/sdk/objects/hexclave-app)). For enhanced security, disable the `Allow all localhost callbacks for development` option. ## OAuth providers Stack uses shared OAuth keys for development to simplify setup when using "Sign in with Google/GitHub/etc." However, this isn't secure for production as it displays "Stack Development" on the providers' consent screens, making it unclear to users if the OAuth request is genuinely from your site. Thus, you should configure your own OAuth keys with the providers and connect them to Stack. To use your own OAuth provider setups in production, follow these steps for each provider you use: On the provider's website, create an OAuth app and set the callback URL to the corresponding Stack callback URL. Copy the client ID and client secret. [Google OAuth Setup Guide](https://developers.google.com/identity/protocols/oauth2#1.-obtain-oauth-2.0-credentials-from-the-dynamic_data.setvar.console_name-.) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/google ``` [GitHub OAuth Setup Guide](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/github ``` [Facebook OAuth Setup Guide](https://developers.facebook.com/docs/development/create-an-app/facebook-login-use-case) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/facebook ``` [Microsoft Azure OAuth Setup Guide](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/microsoft ``` [Spotify OAuth Setup Guide](https://developer.spotify.com/documentation/general/guides/app-settings/) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/spotify ``` [Gitlab OAuth Setup Guide](https://docs.gitlab.com/ee/integration/oauth_provider.html) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/gitlab ``` [Bitbucket OAuth Setup Guide](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/bitbucket ``` [LinkedIn OAuth Setup Guide](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?context=linkedin%2Fcontext\&tabs=HTTPS1) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/linkedin ``` [X OAuth Setup Guide](https://developer.x.com/en/docs/apps/overview) Callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/x ``` Go to the `Auth Methods` section in the Stack dashboard, open the provider's settings, switch from shared keys to custom keys, and enter the client ID and client secret. ## Email server For development, Stack uses a shared email server, which sends emails from Stack's domain. This is not ideal for production as users may not trust emails from an unfamiliar domain. You should set up an email server connected to your own domain. Steps to connect your own email server with Stack: 1. **Setup Email Server**: Configure your own email server and connect it to your domain (this step is beyond Stack's documentation scope). 2. **Configure Stack's Email Settings**: Navigate to the `Emails` section in the Stack dashboard, click `Edit` in the `Email Server` section, switch from `Shared` to `Custom SMTP server`, enter your SMTP configurations, and save. ## Enabling production mode After completing the steps above, you can enable production mode on the `Project Settings` tab in the Stack dashboard, ensuring that your website runs securely with Stack in a production environment. # Payments Source: https://docs.hexclave.com/guides/apps/payments/overview Accept payments and manage billing with Hexclave's Stripe integration Hexclave includes a Payments app that handles billing, subscriptions, and one-time purchases through Stripe. Instead of building your own billing system, you define products in the dashboard and Hexclave takes care of checkout, entitlement tracking, subscription lifecycle, and invoicing. This guide walks through how to set up payments, sell products, manage subscriptions, and work with item-based entitlements like credits or seats. ## Getting started Go to the **Apps** section in your dashboard, find **Payments**, and enable it. Open **Payments -> Settings** and follow the Stripe Connect onboarding flow. You'll be asked for business details, bank info, and identity verification. Once approved, payments are live. While building, enable **test mode** in **Payments -> Settings**. All purchases will be free - no real money is charged. You can switch to live when you're ready. Hexclave Payments is currently only available for US-based businesses. Support for other countries is coming soon. ## Core concepts Before writing any code, it helps to understand how the pieces fit together. A **product** is something you sell - a subscription plan, a one-time purchase, or a credit pack. Products can have one or more **prices** (one-time or recurring, in multiple currencies), and they can include **items** - quantifiable entitlements like credits, seats, or API calls. A **product line** groups products that are mutually exclusive. For example, a "Plan" product line might contain Free, Pro, and Enterprise - a customer can only have one at a time. When they upgrade, the old plan is replaced. A **customer** is whoever owns the purchase. This can be a user, a team, or a custom external entity. Here's how these pieces look in practice: And the typical flow to make it all work: 1. You define products and items in the dashboard 2. Your app generates a checkout URL and redirects the user to Stripe 3. The user pays, Stripe notifies Hexclave, and the product is granted 4. Your app reads the customer's products and item balances to control access ## Defining products Configure your products in **Payments -> Products & Items**. Each product has: * **Display name** - What the customer sees * **Customer type** - Whether this product is for users, teams, or custom customers * **Prices** - One or more prices, each with a currency amount and an optional billing interval (day, week, month, or year). Currency amounts are **decimal strings** like `"9.99"` or `"1000"` — not cent integers. Supported currencies: USD, EUR, GBP, JPY, INR, AUD, CAD. * **Included items** - Items granted when the product is purchased, with configurable quantity, repeat schedule, and expiration behavior A few additional options: * **Product lines** - Assign products to a product line to make them mutually exclusive (e.g. plan tiers). Configure lines in **Payments -> Product Lines**. * **Add-ons** - Set **isAddOnTo** to require the customer to already own a specific base product before purchasing this one. * **Free trial** - Give customers a trial period before charging. Can be set on the product or on individual prices. * **Server-only** - Hide the product from client SDK responses. Useful for products that should only be granted programmatically. * **Stackable** - Allow multiple purchases of the same product (default is one per customer). ## Selling a product To sell a product, generate a checkout URL and redirect the user to it. The `createCheckoutUrl` method is available on both user and team objects. ```typescript title="app/components/purchase-button.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function PurchaseButton({ productId }: { productId: string }) { const user = useUser({ or: 'redirect' }); const handlePurchase = async () => { const checkoutUrl = await user.createCheckoutUrl({ productId, returnUrl: window.location.href, }); window.location.href = checkoutUrl; }; return ; } ``` ```typescript title="app/purchase/page.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function PurchasePage() { const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const checkoutUrl = await user.createCheckoutUrl({ productId: "prod_premium_monthly", }); return Upgrade to Premium; } ``` For team purchases, call `createCheckoutUrl` on the team object instead: ```typescript theme={null} const team = user.useTeam(teamId); const checkoutUrl = await team.createCheckoutUrl({ productId }); ``` If you're using a non-JS backend (Python, Go, etc.), call the REST API directly: `POST /api/v1/payments/purchases/create-purchase-url` with `customer_type`, `customer_id`, and `product_id`. See the [REST API overview](/api/overview) for details. ## Checking what a customer owns After a purchase, you'll want to know what the customer has. There are two ways to do this: check their product list, or check a specific item balance. ### Listing products ```typescript theme={null} // Client component (hook - re-renders on changes) const products = user.useProducts(); // Server component const products = await user.listProducts(); ``` Each product in the list includes: * `id` - The product ID (or `null` for inline products) * `displayName` - The product name * `quantity` - How many the customer owns (relevant for stackable products) * `type` - `"one_time"` or `"subscription"` * `subscription` - Subscription details (period end, cancel state) if applicable * `switchOptions` - Other products in the same product line the customer could switch to ### Checking item balances Items are the building blocks of entitlements. When a product includes items like "100 credits" or "5 seats", those are tracked as item balances on the customer. ```typescript theme={null} // Client component (hook - re-renders on changes) const credits = user.useItem("credits"); // Server component const credits = await user.getItem("credits"); ``` An item has two quantity fields: * `quantity` - The raw balance (can be negative if you've consumed more than granted) * `nonNegativeQuantity` - `Math.max(0, quantity)` for display purposes Here's a practical example - showing a credits counter: ```typescript title="app/components/credits-widget.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function CreditsWidget() { const user = useUser({ or: 'redirect' }); const credits = user.useItem("credits"); return (

Available Credits

{credits.nonNegativeQuantity}

); } ``` ### Consuming credits (server-side) When your app needs to consume credits (e.g. when a user sends an AI request), use `tryDecreaseQuantity` on the server. It's atomic and race-condition-safe - it will return `false` and do nothing if the balance would go negative. ```typescript title="lib/credits.ts" theme={null} import { hexclaveServerApp } from "@/stack/server"; export async function consumeCredits(userId: string, amount: number) { const user = await hexclaveServerApp.getUser(userId); if (!user) throw new Error("User not found"); const credits = await user.getItem("credits"); const success = await credits.tryDecreaseQuantity(amount); if (!success) { throw new Error("Insufficient credits"); } return { remaining: credits.quantity }; } ``` Always use `tryDecreaseQuantity()` instead of checking the balance and then decreasing. This prevents race conditions where multiple requests could consume more credits than available. You can also increase a balance with `credits.increaseQuantity(amount)` or decrease without the safety check using `credits.decreaseQuantity(amount)`. ## Managing subscriptions ### Switching plans When products belong to the same product line, customers can switch between them. For example, upgrading from Pro to Enterprise: ```typescript title="app/components/upgrade-button.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function UpgradeButton() { const user = useUser({ or: 'redirect' }); return ( ); } ``` Switching plans requires the customer to have a default payment method saved. The switch happens immediately and Stripe prorates the charge automatically. ### Canceling a subscription `cancelSubscription` is called on the app instance rather than the user object: ```typescript theme={null} "use client"; import { useHexclaveApp } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function CancelButton({ productId }: { productId: string }) { const app = useHexclaveApp(); return ( ); } ``` ```typescript theme={null} // Cancel for the current user await hexclaveServerApp.cancelSubscription({ productId: "prod_pro" }); // Cancel for a team await hexclaveServerApp.cancelSubscription({ productId: "prod_team_plan", teamId: "team-id", }); ``` ## Billing and payment methods Customers can save a payment method for future purchases and plan switches. This is built on Stripe's SetupIntents. ```typescript theme={null} // Create a setup intent const setupIntent = await user.createPaymentMethodSetupIntent(); // setupIntent.clientSecret - use with Stripe Elements to collect card details // setupIntent.stripeAccountId - the connected Stripe account ID // After the user completes Stripe's card form: const paymentMethod = await user.setDefaultPaymentMethodFromSetupIntent( setupIntentId ); // paymentMethod contains: id, brand, last4, exp_month, exp_year ``` To check if a customer has a payment method saved: ```typescript theme={null} // Client component (hook) const billing = user.useBilling(); // Server component const billing = await user.getBilling(); // billing.hasCustomer - whether a Stripe customer exists // billing.defaultPaymentMethod - card details or null ``` ## Invoices List a customer's invoices for displaying billing history: ```typescript theme={null} // Client component (hook) const invoices = user.useInvoices(); // Server component const invoices = await user.listInvoices(); ``` Each invoice includes `createdAt`, `status` (`"draft"`, `"open"`, `"paid"`, `"uncollectible"`, `"void"`), `amountTotal` (in cents), and `hostedInvoiceUrl` (a link to Stripe's hosted invoice page). Invoices support pagination: ```typescript theme={null} const firstPage = await user.listInvoices({ limit: 10 }); const secondPage = await user.listInvoices({ limit: 10, cursor: firstPage.nextCursor, }); ``` Invoices are available for user and team customers only, not custom customers. ## Granting products programmatically Sometimes you need to give a customer a product without going through checkout - free trials, admin grants, promotional offers, etc. Use `grantProduct` on the server: ```typescript theme={null} // Grant a pre-configured product to a user await hexclaveServerApp.grantProduct({ userId: "user-id", productId: "prod_premium", }); // Grant to a team await hexclaveServerApp.grantProduct({ teamId: "team-id", productId: "prod_team_plan", }); // Grant to a custom customer await hexclaveServerApp.grantProduct({ customCustomerId: "external-org-123", productId: "prod_enterprise", }); ``` You can also grant products with an **inline definition** - no pre-configured product needed. This is useful for one-off grants like bonus credits: ```typescript theme={null} await hexclaveServerApp.grantProduct({ userId: "user-id", product: { display_name: "Bonus Credits", customer_type: "user", server_only: true, stackable: false, prices: { manual: { USD: "0" }, }, included_items: { credits: { quantity: 100 }, }, }, }); ``` If you have a reference to the user object already, you can also call `user.grantProduct({ productId })` directly. ## Customer types Hexclave supports three types of payment customers: * **Users** - Individual user accounts. Users can manage their own purchases, billing, and invoices from the client SDK. * **Teams** - Team or organization accounts. Team admins can create checkouts, switch plans, and cancel subscriptions for their team. * **Custom customers** - External entities identified by an arbitrary string ID. Useful for integrations with external systems. Custom customers can only be managed via the server SDK and do not support billing or invoices. All payment methods (`createCheckoutUrl`, `useProducts`, `useItem`, `switchSubscription`, `useBilling`, `useInvoices`, etc.) are available on both user and team objects. For custom customers, use the top-level `hexclaveServerApp` methods with `customCustomerId`. ## Dashboard The dashboard gives you full visibility and control over your payments: * **Product Lines** - Group products into mutually exclusive tiers. Configure in **Payments -> Product Lines**. * **Products & Items** - Create and edit products, set pricing, and configure included items. In **Payments -> Products & Items**. * **Customers** - View item balances per customer, manually adjust quantities (with optional expiration), and grant products directly. In **Payments -> Customers**. * **Transactions** - See all payment activity, filter by type and customer, view details, and issue refunds. In **Payments -> Transactions**. * **Payouts** - View payout information. In **Payments -> Payouts**. * **Settings** - Connect Stripe, toggle test mode, configure payment methods, and block new purchases. In **Payments -> Settings**. ### Payment emails Email notifications are sent automatically on payment events: * **Payment Receipt** - Sent on successful payment with product details, amount, and receipt link * **Payment Failed** - Sent on failed payment with product name, amount, and failure reason These apply to both one-time purchases and subscription renewals. Customize them in **Emails -> Templates**. ## Test mode During development, enable test mode in **Payments -> Settings**. All purchases will be free and no real money is charged - products are granted immediately without going through Stripe. When you're ready to test with real Stripe flows (but still using test credentials), disable Stack's test mode and use Stripe's test card numbers: * **Success**: `4242 4242 4242 4242` * **Decline**: `4000 0000 0000 0002` * **Insufficient Funds**: `4000 0000 0000 9995` See [Stripe's testing documentation](https://stripe.com/docs/testing) for more test scenarios. # RBAC Permissions Source: https://docs.hexclave.com/guides/apps/rbac/overview Control what each user can do and access within your application Permissions are a way to control what each user can do and access within your application. ## Permission Types Stack supports two types of permissions: 1. **Team Permissions**: Control what a user can do within a specific team 2. **User Permissions**: Control what a user can do globally, across the entire project Both permission types can be managed from the dashboard, and both support arbitrary nesting. ## Team Permissions Team permissions control what a user can do within each team. You can create and assign permissions to team members from the Stack dashboard. These permissions could include actions like `create_post` or `read_secret_info`, or roles like `admin` or `moderator`. Within your app, you can verify if a user has a specific permission within a team. Permissions can be nested to create a hierarchical structure. For example, an `admin` permission can include both `moderator` and `user` permissions. We provide tools to help you verify whether a user has a permission directly or indirectly. ### Creating a Permission To create a new permission, navigate to the `Team Permissions` section of the Stack dashboard. You can select the permissions that the new permission will contain. Any permissions included within these selected permissions will also be recursively included. ### System Permissions Stack comes with a few predefined team permissions known as system permissions. These permissions start with a dollar sign (`$`). While you can assign these permissions to members or include them within other permissions, you cannot modify them as they are integral to the Stack backend system. ### Checking if a User has a Permission To check whether a user has a specific permission, use the `getPermission` method or the `usePermission` hook on the `User` object. This returns the `Permission` object if the user has it; otherwise, it returns `null`. Always perform permission checks on the server side for business logic, as client-side checks can be bypassed. Here's an example: ```tsx title="Check user permission on the client" theme={null} "use client"; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export function CheckUserPermission() { const user = useUser({ or: 'redirect' }); const team = user.useTeam('some-team-id'); const permission = user.usePermission(team, 'read'); // Don't rely on client-side permission checks for business logic. return (
{permission ? 'You have the read permission' : 'You shall not pass'}
); } ```
```tsx title="Check user permission on the server" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function CheckUserPermission() { const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const team = await hexclaveServerApp.getTeam('some-team-id'); const permission = await user.getPermission(team, 'read'); // This is a server-side check, so it's secure. return (
{permission ? 'You have the read permission' : 'You shall not pass'}
); } ```
### Listing All Permissions of a User To get a list of all permissions a user has, use the `listPermissions` method or the `usePermissions` hook on the `User` object. This method retrieves both direct and indirect permissions. Here is an example: ```tsx title="List user permissions on the client" theme={null} "use client"; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export function DisplayUserPermissions() { const user = useUser({ or: 'redirect' }); const permissions = user.usePermissions(); return (
{permissions.map(permission => (
{permission.id}
))}
); } ```
```tsx title="List user permissions on the server" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function DisplayUserPermissions() { const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const permissions = await user.listPermissions(); return (
{permissions.map(permission => (
{permission.id}
))}
); } ```
### Granting a Permission to a User To grant a permission to a user, use the `grantPermission` method on the `ServerUser`. Here's an example: ```tsx theme={null} const team = await hexclaveServerApp.getTeam('teamId'); const user = await hexclaveServerApp.getUser(); await user.grantPermission(team, 'read'); ``` ### Revoking a Permission from a User To revoke a permission from a user, use the `revokePermission` method on the `ServerUser`. Here's an example: ```tsx theme={null} const team = await hexclaveServerApp.getTeam('teamId'); const user = await hexclaveServerApp.getUser(); await user.revokePermission(team, 'read'); ``` ## Project Permissions Project permissions are global permissions that apply to a user across the entire project, regardless of team context. These permissions are useful for handling things like premium plan subscriptions or global admin access. ### Creating a Project Permission To create a new project permission, navigate to the `Project Permissions` section of the Stack dashboard. Similar to team permissions, you can select other permissions that the new permission will contain, creating a hierarchical structure. ### Checking if a User has a Project Permission To check whether a user has a specific project permission, use the `getPermission` method or the `usePermission` hook. Here's an example: ```tsx title="Check user permission on the client" theme={null} "use client"; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export function CheckGlobalPermission() { const user = useUser({ or: 'redirect' }); const permission = user.usePermission('access_admin_dashboard'); return (
{permission ? 'You can access the admin dashboard' : 'Access denied'}
); } ```
```tsx title="Check user permission on the server" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function CheckGlobalPermission() { const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const permission = await user.getPermission('access_admin_dashboard'); return (
{permission ? 'You can access the admin dashboard' : 'Access denied'}
); } ```
### Listing All Project Permissions To get a list of all global permissions a user has, use the `listPermissions` method or the `usePermissions` hook: ```tsx title="List global permissions on the client" theme={null} "use client"; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export function DisplayGlobalPermissions() { const user = useUser({ or: 'redirect' }); const permissions = user.usePermissions(); return (
{permissions.map(permission => (
{permission.id}
))}
); } ```
```tsx title="List global permissions on the server" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function DisplayGlobalPermissions() { const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const permissions = await user.listPermissions(); return (
{permissions.map(permission => (
{permission.id}
))}
); } ```
### Granting a Project Permission To grant a global permission to a user, use the `grantPermission` method: ```tsx theme={null} const user = await hexclaveServerApp.getUser(); await user.grantPermission('access_admin_dashboard'); ``` ### Revoking a Project Permission To revoke a global permission from a user, use the `revokePermission` method: ```tsx theme={null} const user = await hexclaveServerApp.getUser(); await user.revokePermission('access_admin_dashboard'); ``` By following these guidelines, you can efficiently manage and verify both team and user permissions within your application. # Teams Source: https://docs.hexclave.com/guides/apps/teams/overview Manage teams and team members Teams provide a structured way to group users and manage their permissions. Users can belong to multiple teams simultaneously, allowing them to represent departments, B2B customers, or projects. The server can perform all operations on a team, but the client can only carry out some actions if the user has the necessary permissions. This applies to all actions that can be performed on a server/client-side `User` object and a `Team` object. ## Concepts ### Team permissions If you attempt to perform an action without the necessary team permissions, the function will throw an error. Always check if the user has the required permission before performing any action. Learn more about permissions [here](../rbac/overview). Here is an example of how to check if a user has a specific permission on the client ```tsx theme={null} const user = useUser({ or: 'redirect' }); const team = user.useTeam('some-team-id'); if (!team) { return
Team not found
; } const hasPermission = user.usePermission(team, '$invite_members'); if (!hasPermission) { return
No permission
; } // Perform corresponding action like inviting a user ``` ### Team profile A user can have a different profile for each team they belong to (Note this is different to the user's personal profile). This profile contains information like `displayName` and `profileImageUrl`. The team profile can be left empty and it will automatically take the user's personal profile information. The team profile is visible to all the other users in the team that have the `$read_members` permission. ## Retrieving a user's teams You can list all teams a user belongs to using the `listTeams` or `useTeams` functions or fetch a specific team with `getTeam` or `useTeam`. These functions work on both clients and servers. ```tsx theme={null} const user = useUser({ or: 'redirect' }); const allTeams = user.useTeams(); const someTeam = user.useTeam('some-team-id'); // May be null if the user is not a member of this team return (
{allTeams.map(team => (
{team.displayName}
))}
{someTeam ? someTeam.displayName : 'Not a member of this team'}
); ```
```tsx theme={null} const user = await hexclaveServerApp.getUser({ or: 'redirect' }); const allTeams = await user.listTeams(); const someTeam = await user.getTeam('some-team-id'); // May be null if the user is not a member of this team return (
{allTeams.map(team => (
{team.displayName}
))}
{someTeam ? someTeam.displayName : 'Not a member of this team'}
```
## Creating a team To create a team, use the `createTeam` function on the `User` object. The user will be added to the team with the default team creator permissions (You can change this on the permissions tab in the Stack dashboard). On the client side, this requires enabling the "client side team creation" on the team settings tab in the Stack dashboard. ```jsx theme={null} const team = await user.createTeam({ displayName: 'New Team', }); ``` To create a team on the server without adding a specific user, use the `createTeam` function on the `ServerApp` object: ```jsx theme={null} const team = await hexclaveServerApp.createTeam({ displayName: 'New Team', }); ``` ## Updating a team You can update a team with the `update` function on the `Team` object. On the client, the user must have the `$update_team` permission to perform this action. ```tsx theme={null} await team.update({ displayName: 'New Name', }); ``` ## Custom team metadata You can store custom metadata on a team object, similar to the user object. The metadata can be any JSON object. * `clientMetadata`: Can be read and updated on both the client and server sides. * `serverMetadata`: Can only be read and updated on the server side. * `clientReadOnlyMetadata`: Can be read on both the client and server sides, but can only be updated on the server side. ```tsx theme={null} await team.update({ clientMetadata: { customField: 'value', }, }); console.log(team.clientMetadata.customField); // 'value' ``` ## List users in a team You can list all users in a team with the `listUsers` function or the `useUsers` hook on the `Team` object. Note that if you want to get the team profile, you need to get it with `user.teamProfile`. On the client, the current user must have the `$read_members` permission in the team to perform this action. ```tsx theme={null} // ... retrieve the team and ensure user has the necessary permissions const users = team.useUsers(); return (
{users.map(user => (
{user.teamProfile.displayName}
))}
); ```
```tsx theme={null} // ... retrieve the team const users = await team.listUsers(); return (
{users.map(user => (
{user.teamProfile.displayName}
))}
); ```
## Get current user's team profile You can get the current user's team profile with the `getTeamProfile` or `useTeamProfile` function on the `User` object. This function returns the team profile for the team with the given ID. ```tsx theme={null} const teamProfile = user.useTeamProfile(team); ``` ```tsx theme={null} const teamProfile = await user.getTeamProfile(team); ``` ## Invite a user to a team You can invite a user to a team using the `inviteUser` function on the `Team` object. The user will receive an email with a link to join the team. On the client side, the current user must have the `$invite_members` permission to perform this action. ```tsx theme={null} await team.inviteUser(email); ``` ## Adding a user to a team If you want to add a user to a team without sending an email, use the `addUser` function on the `ServerTeam` object. This function can only be called on the server side. ```tsx theme={null} await team.addUser(user.id); ``` ## Removing a user from a team You can remove a user from a team with the `removeUser` function on the `Team` object. On the client side, the current user must have the `$remove_members` permission to perform this action. ```tsx theme={null} await team.removeUser(user.id); ``` ## Leaving a team All users can leave a team without any permissions required. ```tsx theme={null} const team = await user.getTeam('some-team-id'); await user.leaveTeam(team); ``` ## Deleting a team You can delete a team with the `delete` function on the `Team` object. On the client side, the current user must have the `$delete_team` permission to perform this action. ```tsx theme={null} await team.delete(); ``` # Team Selection Source: https://docs.hexclave.com/guides/apps/teams/team-selection A user can be a member of multiple teams, so most websites using teams will need a way to select a "current team" that the user is working on. There are two primary methods to accomplish this: * **Deep Link**: Each team has a unique URL, for example, `your-website.com/team/`. When a team is selected, it redirects to a page with that team's URL. * **Current Team**: When a user selects a team, the app stores the team as a global "current team" state. In this way, the URL of the current team might be something like `your-website.com/current-team`, and the URL won't change after switching teams. ## Deep Link Method The deep link method is generally recommended because it avoids some common issues associated with the current team method. If two users share a link while using deep link URLs, the receiving user will always be directed to the correct team's information based on the link. ## Current Team Method While the current team method can be simpler to implement, it has a downside. If a user shares a link, the recipient might see information about the wrong team (if their "current team" is set differently). This method can also cause problems when a user has multiple browser tabs open with different teams. ## Selected Team Switcher To facilitate team selection, Stack provides a component that looks like this: TeamSwitcher You can import and use the `SelectedTeamSwitcher` component for the "current team" method. It updates the `selectedTeam` when a user selects a team: ```jsx theme={null} import { SelectedTeamSwitcher } from "@hexclave/next"; // replace `next` with the correct framework SDK package export function MyPage() { return (
); } ``` To combine the switcher with the deep link method, you can pass in `urlMap` and `selectedTeam`. The `urlMap` is a function to generate a URL based on the team information, and `selectedTeam` is the team that the user is currently working on. This lets you implement "deep link" + "most recent team". The component will update the `user.selectedTeam` with the `selectedTeam` prop: ```jsx theme={null} `/team/${team.id}`} selectedTeam={team} /> ``` To implement the "deep link" + "default team" method, where you update the `selectedTeam` only when the user clicks "set to default team" or similar, pass `noUpdateSelectedTeam`: ```jsx theme={null} `/team/${team.id}`} selectedTeam={team} noUpdateSelectedTeam /> ``` ## Example: Deep Link + Most Recent Team First, create a page at `/app/team/[teamId]/page.tsx` to display information about a specific team: ```jsx theme={null} "use client"; import { useUser, SelectedTeamSwitcher } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function TeamPage({ params }: { params: { teamId: string } }) { const user = useUser({ or: 'redirect' }); const team = user.useTeam(params.teamId); if (!team) { return
Team not found
; } return (
`/team/${team.id}`} selectedTeam={team} />

Team Name: {team.displayName}

You are a member of this team.

); } ``` Next, create a page to display all teams at `/app/team/page.tsx`: ```jsx theme={null} "use client"; import { useRouter } from "next/navigation"; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function TeamsPage() { const user = useUser({ or: 'redirect' }); const teams = user.useTeams(); const router = useRouter(); const selectedTeam = user.selectedTeam; return (
{selectedTeam && }

All Teams

{teams.map(team => ( ))}
); } ``` Now, if you navigate to `http://localhost:3000/team`, you should be able to see and interact with the teams. # Webhooks Source: https://docs.hexclave.com/guides/apps/webhooks/overview Receive real-time updates when events occur in your Stack project Webhooks are a powerful way to keep your backend in sync with Stack. They allow you to receive real-time updates when events occur in your Stack project, such as when a user or team is created, updated, or deleted. For payload schemas and each webhook event, see the [webhook API reference](/api/webhooks/users/usercreated). ## Setting up webhooks In the Stack dashboard, you can create a webhook endpoint in the "Webhooks" section. After creating this endpoint with your server URL, you will start receiving POST requests with a JSON payload at that endpoint. The event payload will look something like this: ```json theme={null} { "type": "team.created", "data": { "id": "2209422a-eef7-4668-967d-be79409972c5", "display_name": "My Team", ... } } ``` ## Testing webhooks locally You can use services like [Svix Playground](https://www.svix.com/play/) or [Webhook.site](https://webhook.site/) to test the receiving of webhooks or relay them to your local development environment. ## Verifying webhooks To ensure the webhook is coming from Stack (and not from a malicious actor) and is not prone to replay attacks, you should verify the request. Stack signs the webhook payload with a secret key that you can find in the endpoint details on the dashboard. You can verify the signature using the Svix client library. Check out the [Svix documentation](https://docs.svix.com/receiving/verifying-payloads/how) for instructions on how to verify the signature in JavaScript, Python, Ruby, and other languages. Here are example handlers across the supported frameworks: ```tsx Next.js theme={null} // app/api/webhooks/stack/route.ts import { Webhook } from "svix"; export async function POST(request: Request) { const secret = process.env.STACK_WEBHOOK_SECRET!; const payload = await request.text(); const headers = { "svix-id": request.headers.get("svix-id") ?? "", "svix-timestamp": request.headers.get("svix-timestamp") ?? "", "svix-signature": request.headers.get("svix-signature") ?? "", }; const wh = new Webhook(secret); // Throws on error, returns the verified content on success. const verifiedPayload = wh.verify(payload, headers); return Response.json({ ok: true, type: verifiedPayload.type }); } ``` ```tsx React theme={null} // server/webhooks.ts // Webhooks must be received on a server, even if your frontend is a React app. import express from "express"; import { Webhook } from "svix"; const app = express(); app.use("/api/webhooks/stack", express.text({ type: "application/json" })); app.post("/api/webhooks/stack", (req, res) => { const wh = new Webhook(process.env.STACK_WEBHOOK_SECRET!); const verifiedPayload = wh.verify(req.body, { "svix-id": req.header("svix-id") ?? "", "svix-timestamp": req.header("svix-timestamp") ?? "", "svix-signature": req.header("svix-signature") ?? "", }); res.json({ ok: true, type: verifiedPayload.type }); }); ``` ```javascript Express theme={null} import express from "express"; import { Webhook } from "svix"; const app = express(); app.use("/api/webhooks/stack", express.text({ type: "application/json" })); app.post("/api/webhooks/stack", (req, res) => { const wh = new Webhook(process.env.STACK_WEBHOOK_SECRET); const verifiedPayload = wh.verify(req.body, { "svix-id": req.header("svix-id") ?? "", "svix-timestamp": req.header("svix-timestamp") ?? "", "svix-signature": req.header("svix-signature") ?? "", }); res.json({ ok: true, type: verifiedPayload.type }); }); ``` ```javascript Node.js theme={null} import { createServer } from "node:http"; import { Webhook } from "svix"; createServer(async (req, res) => { if (req.method !== "POST" || req.url !== "/api/webhooks/stack") { res.writeHead(404).end(); return; } const payload = await new Promise((resolve, reject) => { let body = ""; req.setEncoding("utf8"); req.on("data", (chunk) => { body += chunk; }); req.on("end", () => resolve(body)); req.on("error", reject); }); const wh = new Webhook(process.env.STACK_WEBHOOK_SECRET); const verifiedPayload = wh.verify(payload, { "svix-id": req.headers["svix-id"] ?? "", "svix-timestamp": req.headers["svix-timestamp"] ?? "", "svix-signature": req.headers["svix-signature"] ?? "", }); res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, type: verifiedPayload.type })); }).listen(3000); ``` ```javascript Vanilla JavaScript theme={null} // server.js // Browser-only JavaScript apps still need a server endpoint to receive webhooks. import { createServer } from "node:http"; import { Webhook } from "svix"; createServer(async (req, res) => { if (req.method !== "POST" || req.url !== "/api/webhooks/stack") { res.writeHead(404).end(); return; } const payload = await new Promise((resolve, reject) => { let body = ""; req.setEncoding("utf8"); req.on("data", (chunk) => { body += chunk; }); req.on("end", () => resolve(body)); req.on("error", reject); }); const wh = new Webhook(process.env.STACK_WEBHOOK_SECRET); const verifiedPayload = wh.verify(payload, { "svix-id": req.headers["svix-id"] ?? "", "svix-timestamp": req.headers["svix-timestamp"] ?? "", "svix-signature": req.headers["svix-signature"] ?? "", }); res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, type: verifiedPayload.type })); }).listen(3000); ``` ```python Django theme={null} from django.conf import settings from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from svix.webhooks import Webhook @csrf_exempt def stack_webhook(request): wh = Webhook(settings.STACK_WEBHOOK_SECRET) verified_payload = wh.verify( request.body.decode("utf-8"), { "svix-id": request.headers["svix-id"], "svix-timestamp": request.headers["svix-timestamp"], "svix-signature": request.headers["svix-signature"], }, ) return JsonResponse({"ok": True, "type": verified_payload["type"]}) ``` ```python FastAPI theme={null} from fastapi import FastAPI, Request from svix.webhooks import Webhook app = FastAPI() @app.post("/api/webhooks/stack") async def stack_webhook(request: Request): payload = await request.body() wh = Webhook(STACK_WEBHOOK_SECRET) verified_payload = wh.verify( payload.decode("utf-8"), { "svix-id": request.headers["svix-id"], "svix-timestamp": request.headers["svix-timestamp"], "svix-signature": request.headers["svix-signature"], }, ) return {"ok": True, "type": verified_payload["type"]} ``` ```python Flask theme={null} from flask import Flask, jsonify, request from svix.webhooks import Webhook app = Flask(__name__) @app.post("/api/webhooks/stack") def stack_webhook(): wh = Webhook(STACK_WEBHOOK_SECRET) verified_payload = wh.verify( request.get_data(as_text=True), { "svix-id": request.headers["svix-id"], "svix-timestamp": request.headers["svix-timestamp"], "svix-signature": request.headers["svix-signature"], }, ) return jsonify({"ok": True, "type": verified_payload["type"]}) ``` If you do not want to install the Svix client library or are using a language that is not supported, you can [verify the signature manually](https://docs.svix.com/receiving/verifying-payloads/how-manual). ## Event types These are the `type` values you may receive. Each links to its API reference page. * [`user.created`](/api/webhooks/users/usercreated) * [`user.updated`](/api/webhooks/users/userupdated) * [`user.deleted`](/api/webhooks/users/userdeleted) * [`team.created`](/api/webhooks/teams/teamcreated) * [`team.updated`](/api/webhooks/teams/teamupdated) * [`team.deleted`](/api/webhooks/teams/teamdeleted) * [`team_membership.created`](/api/webhooks/teams/team_membershipcreated) * [`team_membership.deleted`](/api/webhooks/teams/team_membershipdeleted) * [`team_permission.created`](/api/webhooks/teams/team_permissioncreated) * [`team_permission.deleted`](/api/webhooks/teams/team_permissiondeleted) ## Examples Some members of the community have shared their webhook implementations. For example, [here is an example by Clark Gredona](https://gist.github.com/clarkg/56ffad44949826ae3efe0a431b6021c4) that validates the Webhook schema and update a database user. # Using Hexclave with AI Source: https://docs.hexclave.com/guides/getting-started/ai-integration Teach your coding agent how to use Hexclave — via web fetch, an installable skill, or the Hexclave MCP server. This page is for giving your own coding agent (Claude Code, Cursor, Codex, opencode, …) ongoing knowledge of Hexclave so it writes Hexclave code correctly. If you would like to use Hexclave to power your own CLI, see the [CLI Authentication](/guides/apps/authentication/cli-authentication) page. There are three ways to give a coding agent working knowledge of Hexclave. They differ only in how the instructions reach the agent — the knowledge is the same. Paste a one-line prompt — your agent fetches the live skill on demand. Install the Hexclave `SKILL.md` file so your agent loads it automatically. Connect your agent to the Hexclave MCP server for live docs and `ask_hexclave` search. ## Option 1: Web fetch (recommended) Most coding agents now support web fetch out of the box, so you can simply tell your agent to use Hexclave's skill from the internet. To do so, paste the prompt below into your coding agent: ```markdown title="One-shot prompt" theme={null} Fetch https://skill.hexclave.com and follow it for this task. ``` The fetched `SKILL.md` indexes the entire Hexclave docs sidebar and tells the agent to pull fresh content for whichever surface you're touching (auth, orgs/teams, payments, emails, analytics, the CLI). It is purely a reference — paste it whenever you want the agent to use Hexclave knowledge. ## Option 2: Skill Install the skill once so your agent loads it automatically every time Hexclave is relevant — no copy-paste required. The fastest way to install the Hexclave skill for a Claude Code-style agent or similar: ```sh title="Terminal" theme={null} npx -y skills add hexclave/hexclave --skill stack-auth ``` This detects your agent and installs the skill in the correct location (per-project or global) automatically. From then on, your agent picks it up whenever the conversation touches Hexclave. If your agent can't run the `npx` command above, paste the prompt below into the chat. The agent will fetch `SKILL.md` and drop it into the right location for your agent — per-project if a project-scoped agent folder exists, global otherwise. ````markdown title="Skill install prompt" theme={null} Install the Hexclave skill (`SKILL.md`) for me so you load it automatically on future Hexclave tasks. The canonical file lives at: https://raw.githubusercontent.com/hexclave/hexclave/dev/skills/hexclave/SKILL.md These are the same install locations the `skills` CLI (https://github.com/vercel-labs/skills) uses — match them exactly so the file is picked up the same way. Do this in one shot using smart defaults; only stop to ask a single multiple-choice question at the end if I want to change anything. 1. **Detect the agent.** Identify which coding agent is running you (Claude Code, Codex, Cursor, opencode, Windsurf, Roo, Kilo, Amp, Antigravity, Cline, Continue, Crush, Augment, etc.). If you genuinely can't tell, default to Claude Code. 2. **Pick the default scope.** Look for an existing agent folder in the current project root: `.claude/`, `.agents/`, `.cursor/`, `.codex/`, `.opencode/`, `.windsurf/`, `.roo/`, `.kilocode/`, `.augment/`, `.continue/`, `.crush/`, etc. - If any such folder exists → default to **project** scope. - Otherwise → default to **global** scope. 3. **Resolve the install directory.** Write `SKILL.md` to `/stack-auth/SKILL.md`, where `` comes from this table (mirrors `vercel-labs/skills/src/agents.ts`): | Agent | Project `` | Global `` | | --- | --- | --- | | Claude Code | `.claude/skills` | `~/.claude/skills` (or `$CLAUDE_CONFIG_DIR/skills`) | | Codex | `.agents/skills` | `~/.codex/skills` (or `$CODEX_HOME/skills`) | | Cursor | `.agents/skills` | `~/.cursor/skills` | | opencode | `.agents/skills` | `~/.config/opencode/skills` (XDG) | | Windsurf | `.windsurf/skills` | `~/.codeium/windsurf/skills` | | Amp | `.agents/skills` | `~/.config/agents/skills` (XDG) | | Antigravity | `.agents/skills` | `~/.gemini/antigravity/skills` | | Cline | `.agents/skills` | `~/.agents/skills` | | Roo Code | `.roo/skills` | `~/.roo/skills` | | Kilo Code | `.kilocode/skills` | `~/.kilocode/skills` | | Augment | `.augment/skills` | `~/.augment/skills` | | Continue | `.continue/skills` | `~/.continue/skills` | | Crush | `.crush/skills` | `~/.config/crush/skills` | For any agent not listed, follow its documented skills directory or fall back to `.agents/skills` (project) / `~/./skills` (global). 4. **Install.** Download `SKILL.md` once to a stable cache location — `~/.cache/stack-auth/SKILL.md` (or `$XDG_CACHE_HOME/stack-auth/SKILL.md`) — then create a **symlink** at `/stack-auth/SKILL.md` pointing to that cached file. Create missing parent directories. If the target path already exists, replace it (overwrite the symlink, or back up and replace a regular file). Symlinking means future re-installs at additional scopes/agents reuse the same source file, and re-running the prompt after an update to the cached file picks up automatically. If the platform doesn't support symlinks (e.g. Windows without dev mode), fall back to copying the file verbatim. 5. **Confirm and offer alternatives.** Print the absolute symlink path and the cache target it points to. Then ask me a single multiple-choice question covering anything I might want to change, e.g.: ``` Installed Hexclave skill at . Want to change anything? a) Keep it — done b) Also symlink at the opposite scope () c) Install for a different agent instead d) Symlink into additional agents too ``` Only act on whichever letter I pick. Don't pre-ask before the first install. Additional installs just create more symlinks pointing at the same cached `SKILL.md`. Installing the file is the only goal of this prompt — do not act on the skill's contents yet. ```` ## Option 3: MCP The Hexclave MCP server (`https://mcp.hexclave.com/mcp`) gives your agent live access to Hexclave docs and skill via prompts, resources and an `ask_hexclave` tool that searches the docs with citations. Install it once per agent — it stays available across every project. Opens Cursor and adds `stack-auth` to your MCP config. Opens VS Code and adds `stack-auth` to your MCP config. For the up-to-date list of one-click install buttons for every client (Cursor, VS Code, Claude Desktop, Windsurf, ChatGPT, Gemini, …), visit [`mcp.hexclave.com`](https://mcp.hexclave.com) in your browser. Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project): ```json title="mcp.json" theme={null} { "mcpServers": { "stack-auth": { "url": "https://mcp.hexclave.com/mcp" } } } ``` ```sh title="Terminal" theme={null} claude mcp add --transport http stack-auth https://mcp.hexclave.com/mcp ``` ```sh title="Terminal" theme={null} code --add-mcp '{"type":"http","name":"stack-auth","url":"https://mcp.hexclave.com/mcp"}' ``` ```sh title="Terminal" theme={null} codex mcp add stack-auth --url https://mcp.hexclave.com/mcp ``` Or add to `~/.codex/config.toml`: ```toml title="config.toml" theme={null} [mcp_servers.stack-auth] url = "https://mcp.hexclave.com/mcp" ``` ```json title="mcp.json" theme={null} { "mcpServers": { "stack-auth": { "serverUrl": "https://mcp.hexclave.com/mcp" } } } ``` Add to `~/.gemini/settings.json`: ```json title="settings.json" theme={null} { "mcpServers": { "stack-auth": { "httpUrl": "https://mcp.hexclave.com/mcp", "headers": { "Accept": "application/json, text/event-stream" } } } } ``` For Claude Desktop, ChatGPT, and copy-paste markdown blocks you can drop into your project README, visit [`mcp.hexclave.com`](https://mcp.hexclave.com). If your agent can edit its own config, paste this prompt: ```markdown title="MCP install prompt" theme={null} Install the Hexclave MCP server for me. The server URL is https://mcp.hexclave.com/mcp (HTTP transport). Detect which coding agent I'm using and add the server to the correct config file: - Claude Code → run `claude mcp add --transport http stack-auth https://mcp.hexclave.com/mcp` - Cursor → `~/.cursor/mcp.json` - VS Code → run `code --add-mcp '{"type":"http","name":"stack-auth","url":"https://mcp.hexclave.com/mcp"}'` - Codex → `~/.codex/config.toml` - opencode → `~/.config/opencode/opencode.json` - Gemini CLI → `~/.gemini/settings.json` If this repo already has a project-scoped MCP config (`.cursor/mcp.json`, `.vscode/mcp.json`, `.claude.json`, `.codex/config.toml`, etc.), install it there instead of the global location. After installing, confirm by listing the registered MCP servers. ``` ## Project vs. global scope The install prompts above follow the same rule for picking project-vs-global scope: | Detected in project root | Skill / MCP install scope | | ------------------------------------------------------------------- | ----------------------------------------------------- | | `.claude/`, `.cursor/`, `.vscode/mcp.json`, `.codex/`, `.opencode/` | **Project** (inside the existing folder) | | None of the above | **Global** (user-level config for the detected agent) | This keeps shared repos clean (project-scoped config lives next to the code) and keeps your personal projects ergonomic (one global install, available everywhere). # Setup Source: https://docs.hexclave.com/guides/getting-started/setup Install and configure Hexclave for your project

Setting up with AI? Use this single prompt:

Choose your tech stack

Choose all that apply.

Filter:

Frontend

Backend

Database

Other

Select a tool to show setup instructions.

Setting up with AI? Use this single prompt in your coding agent to set up Hexclave for your selected stack.
## Next.js SDK Setup Instructions Follow these instructions in order to set up and get started with the Hexclave SDK for Next.js . Note: These instructions are for setting up the Hexclave SDK to build your own CLIs. If you're looking to use the Hexclave CLI instead, see the [CLI documentation](https://docs.hexclave.com/guides/going-further/cli). First, install the `@hexclave/next` npm package with your preferred package manager: ```sh theme={null} npm i @hexclave/next # or: pnpm i @hexclave/next # or: yarn add @hexclave/next # or: bun add @hexclave/next ``` Next, let us create the Hexclave App object for your project. This is the most important object in a Hexclave project. In a frontend where you cannot keep a secret key safe, you would use the `HexclaveClientApp` constructor: ```ts src/hexclave/client.ts theme={null} import { HexclaveClientApp } from "@hexclave/next"; export const hexclaveClientApp = new HexclaveClientApp({ tokenStore: "cookie", // "nextjs-cookie" for Next.js, "cookie" for other web frontends, null for backend environments urls: { default: { type: "hosted", } }, }); ``` In a backend where you can keep a secret key safe, you can use the `HexclaveServerApp`, which provides access to more sensitive APIs compared to `HexclaveClientApp`: ```ts src/hexclave/server.ts theme={null} import { HexclaveServerApp } from "@hexclave/next"; import { hexclaveClientApp } from "./client"; export const hexclaveServerApp = new HexclaveServerApp({ inheritsFrom: hexclaveClientApp, }); ``` It's now time to connect your code to a Hexclave project. You can either run Hexclave's dev environment locally, or connect to a production project hosted in the cloud. First, create a `hexclave.config.ts` configuration file in the root directory of the workspace (or anywhere else): ```ts hexclave.config.ts theme={null} import type { HexclaveConfig } from "@hexclave/next/config"; // default: show-onboarding, which shows the onboarding flow for this project when Hexclave starts export const config: HexclaveConfig = "show-onboarding"; ``` The `/config` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with `defineHexclaveConfig` imported from the same `@hexclave/next/config` path (never from `@hexclave/next` directly, which would pull in the whole SDK and fail to load). To run your application with Hexclave, you can then start the dev environment and set environment variables expected by your application. Hexclave's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json: ```sh theme={null} npm i -D @hexclave/cli # or: pnpm i -D @hexclave/cli # or: yarn add -D @hexclave/cli # or: bun add --dev @hexclave/cli ``` ```json package.json theme={null} { // ... "scripts": { // ... "dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:inner", "dev:inner": "" } } ``` `hexclave dev` injects all necessary environment variables into the app process automatically, so the app is ready to use without any extra environment variable setup. If you're looking to run a production version of your application, or the local dashboard doesn't work for you, you can also connect to Hexclave's cloud directly. This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not). #### Frontend Go to your project's dashboard on [app.hexclave.com](https://app.hexclave.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} NEXT_PUBLIC_HEXCLAVE_PROJECT_ID= ``` Alternatively, you can also just set the project ID in the `hexclave/client.ts` file: ```ts src/hexclave/client.ts theme={null} export const hexclaveClientApp = new HexclaveClientApp({ // ... projectId: "your-project-id", }); ``` #### Backend (or both frontend and backend) First, navigate to the [Project Keys](https://app.hexclave.com/projects/-selector-/project-keys) page in the Hexclave dashboard and generate a new set of keys. Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} NEXT_PUBLIC_HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` They'll automatically be picked up by the `HexclaveServerApp` constructor. In React frameworks, Hexclave provides `HexclaveProvider` and `HexclaveTheme` components that should wrap your entire app at the root level. You can do this in the `layout.tsx` file in the `app` directory. The root layout must render the `` and `` tags, and `HexclaveProvider`/`HexclaveTheme` must go inside: ```tsx src/app/layout.tsx theme={null} import { HexclaveProvider, HexclaveTheme } from "@hexclave/next"; import { hexclaveServerApp } from "@/hexclave/server"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Hexclave also provides additional `useXyz` React hooks for `getXyz`/`listXyz` functions. For example, `useUser` is like `getUser`, but as a suspending React hook. To support the suspension, you need to add a suspense boundary around your app. In Next.js, this can be easily done by adding a `loading.tsx` file in the `app` directory: ```tsx src/app/loading.tsx theme={null} export default function Loading() { return
Loading...
; } ```
## React SDK Setup Instructions Follow these instructions in order to set up and get started with the Hexclave SDK for React . Note: These instructions are for setting up the Hexclave SDK to build your own CLIs. If you're looking to use the Hexclave CLI instead, see the [CLI documentation](https://docs.hexclave.com/guides/going-further/cli). First, install the `@hexclave/react` npm package with your preferred package manager: ```sh theme={null} npm i @hexclave/react # or: pnpm i @hexclave/react # or: yarn add @hexclave/react # or: bun add @hexclave/react ``` Next, let us create the Hexclave App object for your project. This is the most important object in a Hexclave project. In a frontend where you cannot keep a secret key safe, you would use the `HexclaveClientApp` constructor: ```ts src/hexclave/client.ts theme={null} import { HexclaveClientApp } from "@hexclave/react"; export const hexclaveClientApp = new HexclaveClientApp({ tokenStore: "cookie", // "nextjs-cookie" for Next.js, "cookie" for other web frontends, null for backend environments urls: { default: { type: "hosted", } }, }); ``` It's now time to connect your code to a Hexclave project. You can either run Hexclave's dev environment locally, or connect to a production project hosted in the cloud. First, create a `hexclave.config.ts` configuration file in the root directory of the workspace (or anywhere else): ```ts hexclave.config.ts theme={null} import type { HexclaveConfig } from "@hexclave/react/config"; // default: show-onboarding, which shows the onboarding flow for this project when Hexclave starts export const config: HexclaveConfig = "show-onboarding"; ``` The `/config` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with `defineHexclaveConfig` imported from the same `@hexclave/react/config` path (never from `@hexclave/react` directly, which would pull in the whole SDK and fail to load). To run your application with Hexclave, you can then start the dev environment and set environment variables expected by your application. Hexclave's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json: ```sh theme={null} npm i -D @hexclave/cli # or: pnpm i -D @hexclave/cli # or: yarn add -D @hexclave/cli # or: bun add --dev @hexclave/cli ``` ```json package.json theme={null} { // ... "scripts": { // ... "dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:inner", "dev:inner": "" } } ``` `hexclave dev` injects all necessary environment variables into the app process automatically, so the app is ready to use without any extra environment variable setup. If you're looking to run a production version of your application, or the local dashboard doesn't work for you, you can also connect to Hexclave's cloud directly. This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not). #### Frontend Go to your project's dashboard on [app.hexclave.com](https://app.hexclave.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} # note: prefix the environment variable with NEXT_PUBLIC_ or VITE_ if your framework requires you to do so HEXCLAVE_PROJECT_ID= ``` Alternatively, you can also just set the project ID in the `hexclave/client.ts` file: ```ts src/hexclave/client.ts theme={null} export const hexclaveClientApp = new HexclaveClientApp({ // ... projectId: "your-project-id", }); ``` #### Backend (or both frontend and backend) First, navigate to the [Project Keys](https://app.hexclave.com/projects/-selector-/project-keys) page in the Hexclave dashboard and generate a new set of keys. Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} # as above, prefix the project ID environment variable with NEXT_PUBLIC_ or VITE_ if your framework requires you to do so # do NOT prefix the secret server key environment variable with NEXT_PUBLIC_ or VITE_ as it is server-only HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` They'll automatically be picked up by the `HexclaveServerApp` constructor. In React frameworks, Hexclave provides `HexclaveProvider` and `HexclaveTheme` components that should wrap your entire app at the root level. For example, if you have an `App.tsx` file, update it as follows: ```tsx src/App.tsx theme={null} import { HexclaveProvider, HexclaveTheme } from "@hexclave/react"; import { hexclaveClientApp } from "./hexclave/client"; export default function App() { return ( {/* your app content */} ); } ``` Hexclave also provides additional `useXyz` React hooks for `getXyz`/`listXyz` functions. For example, `useUser` is like `getUser`, but as a suspending React hook. To support the suspension, you need to add a suspense boundary around your app. The easiest way to do this is to just wrap your entire app in a `Suspense` component: ```tsx src/App.tsx theme={null} import { Suspense } from "react"; import { HexclaveProvider, HexclaveTheme } from "@hexclave/react"; import { hexclaveClientApp } from "./hexclave/client"; export default function App() { return ( Loading...
}> {/* your app content */} ); } ```
## Other JS/TS SDK Setup Instructions Follow these instructions in order to set up and get started with the Hexclave SDK for Other JS/TS . Note: These instructions are for setting up the Hexclave SDK to build your own CLIs. If you're looking to use the Hexclave CLI instead, see the [CLI documentation](https://docs.hexclave.com/guides/going-further/cli). First, install the `@hexclave/js` npm package with your preferred package manager: ```sh theme={null} npm i @hexclave/js # or: pnpm i @hexclave/js # or: yarn add @hexclave/js # or: bun add @hexclave/js ``` Next, let us create the Hexclave App object for your project. This is the most important object in a Hexclave project. In a frontend where you cannot keep a secret key safe, you would use the `HexclaveClientApp` constructor: ```ts src/hexclave/client.ts theme={null} import { HexclaveClientApp } from "@hexclave/js"; export const hexclaveClientApp = new HexclaveClientApp({ tokenStore: "cookie", // "nextjs-cookie" for Next.js, "cookie" for other web frontends, null for backend environments urls: { default: { type: "hosted", } }, }); ``` In a backend where you can keep a secret key safe, you can use the `HexclaveServerApp`, which provides access to more sensitive APIs compared to `HexclaveClientApp`: ```ts src/hexclave/server.ts theme={null} import { HexclaveServerApp } from "@hexclave/js"; export const hexclaveServerApp = new HexclaveServerApp({ tokenStore: null, urls: { default: { type: "hosted", } }, }); ``` In frameworks that are both front- and backend, like Next.js, you can also create a `HexclaveServerApp` from a `HexclaveClientApp` object: ```ts src/hexclave/server.ts theme={null} import { HexclaveServerApp } from "@hexclave/js"; import { hexclaveClientApp } from "./client"; export const hexclaveServerApp = new HexclaveServerApp({ inheritsFrom: hexclaveClientApp, }); ``` It's now time to connect your code to a Hexclave project. You can either run Hexclave's dev environment locally, or connect to a production project hosted in the cloud. First, create a `hexclave.config.ts` configuration file in the root directory of the workspace (or anywhere else): ```ts hexclave.config.ts theme={null} import type { HexclaveConfig } from "@hexclave/js/config"; // default: show-onboarding, which shows the onboarding flow for this project when Hexclave starts export const config: HexclaveConfig = "show-onboarding"; ``` The `/config` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with `defineHexclaveConfig` imported from the same `@hexclave/js/config` path (never from `@hexclave/js` directly, which would pull in the whole SDK and fail to load). To run your application with Hexclave, you can then start the dev environment and set environment variables expected by your application. Hexclave's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json: ```sh theme={null} npm i -D @hexclave/cli # or: pnpm i -D @hexclave/cli # or: yarn add -D @hexclave/cli # or: bun add --dev @hexclave/cli ``` ```json package.json theme={null} { // ... "scripts": { // ... "dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:inner", "dev:inner": "" } } ``` `hexclave dev` injects all necessary environment variables into the app process automatically, so the app is ready to use without any extra environment variable setup. If you're looking to run a production version of your application, or the local dashboard doesn't work for you, you can also connect to Hexclave's cloud directly. This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not). #### Frontend Go to your project's dashboard on [app.hexclave.com](https://app.hexclave.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} # note: prefix the environment variable with NEXT_PUBLIC_ or VITE_ if your framework requires you to do so HEXCLAVE_PROJECT_ID= ``` Alternatively, you can also just set the project ID in the `hexclave/client.ts` file: ```ts src/hexclave/client.ts theme={null} export const hexclaveClientApp = new HexclaveClientApp({ // ... projectId: "your-project-id", }); ``` #### Backend (or both frontend and backend) First, navigate to the [Project Keys](https://app.hexclave.com/projects/-selector-/project-keys) page in the Hexclave dashboard and generate a new set of keys. Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} # as above, prefix the project ID environment variable with NEXT_PUBLIC_ or VITE_ if your framework requires you to do so # do NOT prefix the secret server key environment variable with NEXT_PUBLIC_ or VITE_ as it is server-only HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` They'll automatically be picked up by the `HexclaveServerApp` constructor. You are now ready to use the Hexclave SDK. If you have any frontends calling your backend endpoints, you may want to pass along the Hexclave tokens in a header such that you can access the same user object on your backend. The most ergonomic way to do this is to pass the result of `hexclaveClientApp.getAuthorizationHeader()` as the `Authorization` header into your backend endpoints when the user is signed in: ```ts theme={null} // NOTE: This is your frontend's code const authorizationHeader = await hexclaveClientApp.getAuthorizationHeader(); const response = await fetch("/my-backend-endpoint", { headers: { ...(authorizationHeader ? { Authorization: authorizationHeader } : {}), }, }); // ... ``` In most backend frameworks you can then access the user object by passing the request object as a `tokenStore` of the functions that access the user object: ```ts theme={null} // NOTE: This is your backend's code const user = await hexclaveServerApp.getUser({ tokenStore: request }); return new Response("Hello, " + user.displayName, { headers: { "Cache-Control": "private, no-store" } }); ``` This will work as long as `request` is an object that follows the shape `{ headers: Record | { get: (name: string) => string | null } }`. > Make sure that HTTP caching is disabled with `Cache-Control: private, no-store` for authenticated backend endpoints. If you cannot use `getAuthorizationHeader()`, for example because you are using a protocol other than HTTP, you can use `getAuthJson()` instead: ```ts theme={null} // Frontend: await rpcCall("my-rpc-endpoint", { data: { auth: await hexclaveClientApp.getAuthJson(), }, }); // Backend: const user = await hexclaveServerApp.getUser({ tokenStore: data.auth }); return new RpcResponse("Hello, " + user.displayName); ```
## Tanstack Start SDK Setup Instructions Follow these instructions in order to set up and get started with the Hexclave SDK for Tanstack Start . Note: These instructions are for setting up the Hexclave SDK to build your own CLIs. If you're looking to use the Hexclave CLI instead, see the [CLI documentation](https://docs.hexclave.com/guides/going-further/cli). First, install the `@hexclave/tanstack-start` npm package with your preferred package manager: ```sh theme={null} npm i @hexclave/tanstack-start # or: pnpm i @hexclave/tanstack-start # or: yarn add @hexclave/tanstack-start # or: bun add @hexclave/tanstack-start ``` Next, let us create the Hexclave App object for your project. This is the most important object in a Hexclave project. In a frontend where you cannot keep a secret key safe, you would use the `HexclaveClientApp` constructor: ```ts src/hexclave/client.ts theme={null} import { HexclaveClientApp } from "@hexclave/tanstack-start"; export const hexclaveClientApp = new HexclaveClientApp({ tokenStore: "cookie", // "nextjs-cookie" for Next.js, "cookie" for other web frontends, null for backend environments urls: { default: { type: "hosted", } }, }); ``` It's now time to connect your code to a Hexclave project. You can either run Hexclave's dev environment locally, or connect to a production project hosted in the cloud. First, create a `hexclave.config.ts` configuration file in the root directory of the workspace (or anywhere else): ```ts hexclave.config.ts theme={null} import type { HexclaveConfig } from "@hexclave/tanstack-start/config"; // default: show-onboarding, which shows the onboarding flow for this project when Hexclave starts export const config: HexclaveConfig = "show-onboarding"; ``` The `/config` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with `defineHexclaveConfig` imported from the same `@hexclave/tanstack-start/config` path (never from `@hexclave/tanstack-start` directly, which would pull in the whole SDK and fail to load). To run your application with Hexclave, you can then start the dev environment and set environment variables expected by your application. Hexclave's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json: ```sh theme={null} npm i -D @hexclave/cli # or: pnpm i -D @hexclave/cli # or: yarn add -D @hexclave/cli # or: bun add --dev @hexclave/cli ``` ```json package.json theme={null} { // ... "scripts": { // ... "dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:inner", "dev:inner": "" } } ``` `hexclave dev` injects all necessary environment variables into the app process automatically, so the app is ready to use without any extra environment variable setup. If you're looking to run a production version of your application, or the local dashboard doesn't work for you, you can also connect to Hexclave's cloud directly. This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not). #### Frontend Go to your project's dashboard on [app.hexclave.com](https://app.hexclave.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} VITE_HEXCLAVE_PROJECT_ID= ``` Alternatively, you can also just set the project ID in the `hexclave/client.ts` file: ```ts src/hexclave/client.ts theme={null} export const hexclaveClientApp = new HexclaveClientApp({ // ... projectId: "your-project-id", }); ``` #### Backend (or both frontend and backend) First, navigate to the [Project Keys](https://app.hexclave.com/projects/-selector-/project-keys) page in the Hexclave dashboard and generate a new set of keys. Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} VITE_HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` They'll automatically be picked up by the `HexclaveServerApp` constructor. In React frameworks, Hexclave provides `HexclaveProvider` and `HexclaveTheme` components that should wrap your entire app at the root level. TanStack Start uses file-based routes. The provider goes inside the root route's `component` (the inner React tree), while the document shell stays in `shellComponent`. Update `src/routes/__root.tsx`: ```tsx src/routes/__root.tsx theme={null} import { HexclaveProvider, HexclaveTheme } from "@hexclave/tanstack-start"; import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; import type { ReactNode } from "react"; import { hexclaveClientApp } from "../hexclave/client"; export const Route = createRootRoute({ shellComponent: RootDocument, component: RootComponent, }); function RootDocument({ children }: { children: ReactNode }) { return ( {children} ); } function RootComponent() { return ( ); } ``` Do not edit `src/routeTree.gen.ts` — it is regenerated automatically by the TanStack Start router from the files under `src/routes/`. Hexclave also provides additional `useXyz` React hooks for `getXyz`/`listXyz` functions. For example, `useUser` is like `getUser`, but as a suspending React hook. To support the suspension, you need to add a suspense boundary around your app. wrap the `` in your root route with a `Suspense` boundary so the document shell can stream while child routes wait on Hexclave. Update `RootComponent` in `src/routes/__root.tsx`: ```tsx src/routes/__root.tsx theme={null} import { Suspense } from "react"; // ...other imports... function RootComponent() { return ( Loading...
}> ); } ``` Hexclave's auth flows (sign-in, sign-up, OAuth callbacks, password reset, etc.) are rendered by a single `HexclaveHandler` component mounted at `/handler/*`. In TanStack Start, expose it as a splat file route at `src/routes/handler/$.tsx`: ```tsx src/routes/handler/$.tsx theme={null} import { HexclaveHandler } from "@hexclave/tanstack-start"; import { createFileRoute, useLocation } from "@tanstack/react-router"; export const Route = createFileRoute("/handler/$")({ ssr: false, component: HandlerPage, }); function HandlerPage() { const { pathname } = useLocation(); return ; } ``` Two TanStack-specific notes: * The route is opted out of SSR with `ssr: false`. The handler runs browser-only auth flows (cookies, redirects, popups), so rendering it on the server provides no benefit and can fight with hydration. Other routes can opt into or out of SSR per-route the same way. * Hexclave resolves the current user during SSR by reading TanStack Start's request cookies through `@hexclave/tanstack-start`'s server context. No extra wiring is required — `useUser()` "just works" on both server and client routes as long as `tokenStore: "cookie"` is set on `HexclaveClientApp`.
## Node.js SDK Setup Instructions Follow these instructions in order to set up and get started with the Hexclave SDK for Node.js . Note: These instructions are for setting up the Hexclave SDK to build your own CLIs. If you're looking to use the Hexclave CLI instead, see the [CLI documentation](https://docs.hexclave.com/guides/going-further/cli). First, install the `@hexclave/js` npm package with your preferred package manager: ```sh theme={null} npm i @hexclave/js # or: pnpm i @hexclave/js # or: yarn add @hexclave/js # or: bun add @hexclave/js ``` Next, let us create the Hexclave App object for your project. This is the most important object in a Hexclave project. In a backend where you can keep a secret key safe, you can use the `HexclaveServerApp`, which provides access to more sensitive APIs compared to `HexclaveClientApp`: ```ts src/hexclave/server.ts theme={null} import { HexclaveServerApp } from "@hexclave/js"; export const hexclaveServerApp = new HexclaveServerApp({ tokenStore: null, urls: { default: { type: "hosted", } }, }); ``` It's now time to connect your code to a Hexclave project. You can either run Hexclave's dev environment locally, or connect to a production project hosted in the cloud. First, create a `hexclave.config.ts` configuration file in the root directory of the workspace (or anywhere else): ```ts hexclave.config.ts theme={null} import type { HexclaveConfig } from "@hexclave/js/config"; // default: show-onboarding, which shows the onboarding flow for this project when Hexclave starts export const config: HexclaveConfig = "show-onboarding"; ``` The `/config` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with `defineHexclaveConfig` imported from the same `@hexclave/js/config` path (never from `@hexclave/js` directly, which would pull in the whole SDK and fail to load). To run your application with Hexclave, you can then start the dev environment and set environment variables expected by your application. Hexclave's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json: ```sh theme={null} npm i -D @hexclave/cli # or: pnpm i -D @hexclave/cli # or: yarn add -D @hexclave/cli # or: bun add --dev @hexclave/cli ``` ```json package.json theme={null} { // ... "scripts": { // ... "dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:inner", "dev:inner": "" } } ``` `hexclave dev` injects all necessary environment variables into the app process automatically, so the app is ready to use without any extra environment variable setup. If you're looking to run a production version of your application, or the local dashboard doesn't work for you, you can also connect to Hexclave's cloud directly. This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not). #### Frontend Go to your project's dashboard on [app.hexclave.com](https://app.hexclave.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} # note: prefix the environment variable with NEXT_PUBLIC_ or VITE_ if your framework requires you to do so HEXCLAVE_PROJECT_ID= ``` Alternatively, you can also just set the project ID in the `hexclave/client.ts` file: ```ts src/hexclave/client.ts theme={null} export const hexclaveClientApp = new HexclaveClientApp({ // ... projectId: "your-project-id", }); ``` #### Backend (or both frontend and backend) First, navigate to the [Project Keys](https://app.hexclave.com/projects/-selector-/project-keys) page in the Hexclave dashboard and generate a new set of keys. Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} # as above, prefix the project ID environment variable with NEXT_PUBLIC_ or VITE_ if your framework requires you to do so # do NOT prefix the secret server key environment variable with NEXT_PUBLIC_ or VITE_ as it is server-only HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` They'll automatically be picked up by the `HexclaveServerApp` constructor. You are now ready to use the Hexclave SDK. If you have any frontends calling your backend endpoints, you may want to pass along the Hexclave tokens in a header such that you can access the same user object on your backend. The most ergonomic way to do this is to pass the result of `hexclaveClientApp.getAuthorizationHeader()` as the `Authorization` header into your backend endpoints when the user is signed in: ```ts theme={null} // NOTE: This is your frontend's code const authorizationHeader = await hexclaveClientApp.getAuthorizationHeader(); const response = await fetch("/my-backend-endpoint", { headers: { ...(authorizationHeader ? { Authorization: authorizationHeader } : {}), }, }); // ... ``` In most backend frameworks you can then access the user object by passing the request object as a `tokenStore` of the functions that access the user object: ```ts theme={null} // NOTE: This is your backend's code const user = await hexclaveServerApp.getUser({ tokenStore: request }); return new Response("Hello, " + user.displayName, { headers: { "Cache-Control": "private, no-store" } }); ``` This will work as long as `request` is an object that follows the shape `{ headers: Record | { get: (name: string) => string | null } }`. > Make sure that HTTP caching is disabled with `Cache-Control: private, no-store` for authenticated backend endpoints. If you cannot use `getAuthorizationHeader()`, for example because you are using a protocol other than HTTP, you can use `getAuthJson()` instead: ```ts theme={null} // Frontend: await rpcCall("my-rpc-endpoint", { data: { auth: await hexclaveClientApp.getAuthJson(), }, }); // Backend: const user = await hexclaveServerApp.getUser({ tokenStore: data.auth }); return new RpcResponse("Hello, " + user.displayName); ```
## Bun SDK Setup Instructions Follow these instructions in order to set up and get started with the Hexclave SDK for Bun . Note: These instructions are for setting up the Hexclave SDK to build your own CLIs. If you're looking to use the Hexclave CLI instead, see the [CLI documentation](https://docs.hexclave.com/guides/going-further/cli). First, install the `@hexclave/js` npm package with your preferred package manager: ```sh theme={null} npm i @hexclave/js # or: pnpm i @hexclave/js # or: yarn add @hexclave/js # or: bun add @hexclave/js ``` Next, let us create the Hexclave App object for your project. This is the most important object in a Hexclave project. In a backend where you can keep a secret key safe, you can use the `HexclaveServerApp`, which provides access to more sensitive APIs compared to `HexclaveClientApp`: ```ts src/hexclave/server.ts theme={null} import { HexclaveServerApp } from "@hexclave/js"; export const hexclaveServerApp = new HexclaveServerApp({ tokenStore: null, urls: { default: { type: "hosted", } }, }); ``` It's now time to connect your code to a Hexclave project. You can either run Hexclave's dev environment locally, or connect to a production project hosted in the cloud. First, create a `hexclave.config.ts` configuration file in the root directory of the workspace (or anywhere else): ```ts hexclave.config.ts theme={null} import type { HexclaveConfig } from "@hexclave/js/config"; // default: show-onboarding, which shows the onboarding flow for this project when Hexclave starts export const config: HexclaveConfig = "show-onboarding"; ``` The `/config` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with `defineHexclaveConfig` imported from the same `@hexclave/js/config` path (never from `@hexclave/js` directly, which would pull in the whole SDK and fail to load). To run your application with Hexclave, you can then start the dev environment and set environment variables expected by your application. Hexclave's CLI has a `dev` command does both of these, so let's install it as a dev dependency and wrap your existing `dev` script in your package.json: ```sh theme={null} npm i -D @hexclave/cli # or: pnpm i -D @hexclave/cli # or: yarn add -D @hexclave/cli # or: bun add --dev @hexclave/cli ``` ```json package.json theme={null} { // ... "scripts": { // ... "dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:inner", "dev:inner": "" } } ``` `hexclave dev` injects all necessary environment variables into the app process automatically, so the app is ready to use without any extra environment variable setup. If you're looking to run a production version of your application, or the local dashboard doesn't work for you, you can also connect to Hexclave's cloud directly. This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not). #### Frontend Go to your project's dashboard on [app.hexclave.com](https://app.hexclave.com) and get the project ID. You can find it in the URL after the `/projects/` part. Copy-paste it into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} # note: prefix the environment variable with NEXT_PUBLIC_ or VITE_ if your framework requires you to do so HEXCLAVE_PROJECT_ID= ``` Alternatively, you can also just set the project ID in the `hexclave/client.ts` file: ```ts src/hexclave/client.ts theme={null} export const hexclaveClientApp = new HexclaveClientApp({ // ... projectId: "your-project-id", }); ``` #### Backend (or both frontend and backend) First, navigate to the [Project Keys](https://app.hexclave.com/projects/-selector-/project-keys) page in the Hexclave dashboard and generate a new set of keys. Then, copy-paste them into your `.env.local` file (or wherever your environment variables are stored): ```.env .env.local theme={null} # as above, prefix the project ID environment variable with NEXT_PUBLIC_ or VITE_ if your framework requires you to do so # do NOT prefix the secret server key environment variable with NEXT_PUBLIC_ or VITE_ as it is server-only HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` They'll automatically be picked up by the `HexclaveServerApp` constructor. You are now ready to use the Hexclave SDK. If you have any frontends calling your backend endpoints, you may want to pass along the Hexclave tokens in a header such that you can access the same user object on your backend. The most ergonomic way to do this is to pass the result of `hexclaveClientApp.getAuthorizationHeader()` as the `Authorization` header into your backend endpoints when the user is signed in: ```ts theme={null} // NOTE: This is your frontend's code const authorizationHeader = await hexclaveClientApp.getAuthorizationHeader(); const response = await fetch("/my-backend-endpoint", { headers: { ...(authorizationHeader ? { Authorization: authorizationHeader } : {}), }, }); // ... ``` In most backend frameworks you can then access the user object by passing the request object as a `tokenStore` of the functions that access the user object: ```ts theme={null} // NOTE: This is your backend's code const user = await hexclaveServerApp.getUser({ tokenStore: request }); return new Response("Hello, " + user.displayName, { headers: { "Cache-Control": "private, no-store" } }); ``` This will work as long as `request` is an object that follows the shape `{ headers: Record | { get: (name: string) => string | null } }`. > Make sure that HTTP caching is disabled with `Cache-Control: private, no-store` for authenticated backend endpoints. If you cannot use `getAuthorizationHeader()`, for example because you are using a protocol other than HTTP, you can use `getAuthJson()` instead: ```ts theme={null} // Frontend: await rpcCall("my-rpc-endpoint", { data: { auth: await hexclaveClientApp.getAuthJson(), }, }); // Backend: const user = await hexclaveServerApp.getUser({ tokenStore: data.auth }); return new RpcResponse("Hello, " + user.displayName); ```
## Python Backend Setup Follow these instructions to authenticate requests to a Python backend with Hexclave. This setup is for Python backends that do not use the JavaScript SDK. The backend flow is: your frontend sends the user's access token to your backend, and your backend verifies it before serving protected data. You can use either a development environment with the local dashboard or a Hexclave Cloud project. If this project already has a `hexclave.config.ts` file for another frontend or backend, reuse that same file so the whole project shares one Hexclave config. Otherwise, create a new `hexclave.config.ts` file in your workspace: ```ts hexclave.config.ts theme={null} import type { HexclaveConfig } from "@hexclave/js/config"; export const config: HexclaveConfig = "show-onboarding"; ``` The `/config` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with `defineHexclaveConfig` imported from the same `@hexclave/js/config` path (never from `@hexclave/js` directly, which would pull in the whole SDK and fail to load). Run your backend through the Hexclave CLI so it starts the local dashboard and injects the Hexclave environment variables: ```json package.json theme={null} { "scripts": { "dev": "hexclave dev --config-file ./hexclave.config.ts -- " } } ``` Your backend should read `HEXCLAVE_PROJECT_ID` and `HEXCLAVE_SECRET_SERVER_KEY` from the environment. Create or select a project on [app.hexclave.com](https://app.hexclave.com). Then copy the project ID and a secret server key into your backend environment: ```.env .env theme={null} HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` The secret server key must only be available to your backend. Never expose it to browser code, mobile clients, logs, or public repositories. Install `requests` for REST API verification. If you want to use JWT verification, also install `PyJWT[crypto]`. ```sh theme={null} pip install requests PyJWT[crypto] ``` From your frontend, get the current user's access token and pass it to your backend endpoint. ```ts theme={null} // this is your frontend's code! const { accessToken } = await user.getAuthJson(); const response = await fetch("", { headers: { "x-stack-access-token": accessToken, }, }); ``` Hexclave supports two backend verification approaches. JWT verification is faster and local to your backend. REST endpoint verification asks Hexclave to validate the token and return the current user object. JWT verification validates the token locally in your backend. It does not require a request to Hexclave on every call, but it only gives you the information contained in the token, such as the user ID. ```python theme={null} import os import jwt from jwt import PyJWKClient from jwt.exceptions import InvalidTokenError jwks_client = PyJWKClient( f"https://api.hexclave.com/api/v1/projects/{os.environ['HEXCLAVE_PROJECT_ID']}/.well-known/jwks.json" ) def get_current_user_id_from_jwt(request): access_token = request.headers.get("x-stack-access-token") if not access_token: return None try: signing_key = jwks_client.get_signing_key_from_jwt(access_token) payload = jwt.decode( access_token, signing_key.key, algorithms=["ES256"], audience=os.environ["HEXCLAVE_PROJECT_ID"], ) return payload["sub"] except InvalidTokenError: return None ``` REST endpoint verification asks Hexclave to validate the token and returns the current user object. Use this when you want the complete, up-to-date user profile or do not want to implement JWT verification yourself. ```python theme={null} import os import requests def get_current_hexclave_user(request): access_token = request.headers.get("x-stack-access-token") if not access_token: return None response = requests.get( "https://api.hexclave.com/api/v1/users/me", headers={ "x-stack-access-type": "server", "x-stack-project-id": os.environ["HEXCLAVE_PROJECT_ID"], "x-stack-secret-server-key": os.environ["HEXCLAVE_SECRET_SERVER_KEY"], "x-stack-access-token": access_token, }, timeout=10, ) if response.status_code == 200: return response.json() return None ``` If the response is `200 OK`, the user is authenticated. If the response is not `200 OK`, treat the request as unauthenticated. Wrap your protected endpoints with a helper that extracts `x-stack-access-token`, verifies it with either JWT verification or REST API verification, and returns `401 Unauthorized` when verification fails. > Disable HTTP caching for authenticated responses with a header like `Cache-Control: private, no-store`.
## Other Backend Setup (REST API) Follow these instructions to authenticate requests from any backend language using Hexclave's REST API. Use this option when your backend is not JavaScript/TypeScript or Python, or when you want to call Hexclave over plain HTTP. The backend flow is: your frontend sends the user's access token to your backend, and your backend verifies it before serving protected data. You can use either a development environment with the local dashboard or a Hexclave Cloud project. If this project already has a `hexclave.config.ts` file for another frontend or backend, reuse that same file so the whole project shares one Hexclave config. Otherwise, create a new `hexclave.config.ts` file in your workspace: ```ts hexclave.config.ts theme={null} import type { HexclaveConfig } from "@hexclave/js/config"; export const config: HexclaveConfig = "show-onboarding"; ``` The `/config` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with `defineHexclaveConfig` imported from the same `@hexclave/js/config` path (never from `@hexclave/js` directly, which would pull in the whole SDK and fail to load). Run your backend through the Hexclave CLI so it starts the local dashboard and injects the Hexclave environment variables: ```json package.json theme={null} { "scripts": { "dev": "hexclave dev --config-file ./hexclave.config.ts -- " } } ``` Your backend should read `HEXCLAVE_PROJECT_ID` and `HEXCLAVE_SECRET_SERVER_KEY` from the environment. Create or select a project on [app.hexclave.com](https://app.hexclave.com). Then copy the project ID and a secret server key into your backend environment: ```.env .env theme={null} HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` The secret server key must only be available to your backend. Never expose it to browser code, mobile clients, logs, or public repositories. From your frontend, get the current user's access token and pass it to your backend endpoint. ```ts theme={null} // this is your frontend's code! const { accessToken } = await user.getAuthJson(); const response = await fetch("", { headers: { "x-stack-access-token": accessToken, }, }); ``` Hexclave supports two backend verification approaches. JWT verification is faster and local to your backend. REST endpoint verification asks Hexclave to validate the token and return the current user object. JWT verification validates the token locally in your backend. It does not require a request to Hexclave on every call, but it only gives you the information contained in the token, such as the user ID. ```text theme={null} 1. Read the access token from the `x-stack-access-token` header. 2. Fetch the JWKS from: https://api.hexclave.com/api/v1/projects//.well-known/jwks.json 3. Verify the JWT signature with an ES256-capable JWT library. 4. Verify the token audience is your Hexclave project ID. 5. Use the `sub` claim as the authenticated user ID. 6. Reject the request if any verification step fails. ``` REST endpoint verification asks Hexclave to validate the token and returns the current user object. Use this when you want the complete, up-to-date user profile or do not want to implement JWT verification yourself. ```sh theme={null} curl https://api.hexclave.com/api/v1/users/me \ -H "x-stack-access-type: server" \ -H "x-stack-project-id: $HEXCLAVE_PROJECT_ID" \ -H "x-stack-secret-server-key: $HEXCLAVE_SECRET_SERVER_KEY" \ -H "x-stack-access-token: " ``` If the response is `200 OK`, the user is authenticated. If the response is not `200 OK`, treat the request as unauthenticated. Wrap your protected endpoints with a helper that extracts `x-stack-access-token`, verifies it with either JWT verification or REST API verification, and returns `401 Unauthorized` when verification fails. > Disable HTTP caching for authenticated responses with a header like `Cache-Control: private, no-store`.
## Convex Setup Follow these instructions to integrate Hexclave with Convex. If the project does not already use Convex, initialize a Convex + Next.js app: ```sh theme={null} npm create convex@latest ``` When prompted, choose **Next.js** and **No auth**. Hexclave will provide auth. During development, run the Convex backend and the app dev server: ```sh theme={null} npx convex dev npm run dev ``` Install Hexclave in the app. If you have not already completed the SDK setup steps above, follow the instructions in the [Getting Started Guide](https://docs.hexclave.com/guides/getting-started/setup). Create or update `convex/auth.config.ts`: ```ts convex/auth.config.ts theme={null} import { getConvexProvidersConfig } from "@hexclave/js"; // or: import { getConvexProvidersConfig } from "@hexclave/react"; // or: import { getConvexProvidersConfig } from "@hexclave/next"; export default { providers: getConvexProvidersConfig({ projectId: process.env.HEXCLAVE_PROJECT_ID, // or process.env.NEXT_PUBLIC_HEXCLAVE_PROJECT_ID }), }; ``` Update the Convex client setup so Convex receives Hexclave tokens. In browser JavaScript: ```ts theme={null} convexClient.setAuth(hexclaveClientApp.getConvexClientAuth({})); ``` In React: ```ts theme={null} convexReactClient.setAuth(hexclaveClientApp.getConvexClientAuth({})); ``` For Convex HTTP clients on the server, pass a request-like token store: ```ts theme={null} convexHttpClient.setAuth(hexclaveClientApp.getConvexHttpClientAuth({ tokenStore: requestObject })); ``` In Convex queries and mutations, use Hexclave's Convex integration to read the current user. ```ts convex/myFunctions.ts theme={null} import { query } from "./_generated/server"; import { hexclaveServerApp } from "../src/hexclave/server"; export const myQuery = query({ handler: async (ctx, args) => { const user = await hexclaveServerApp.getPartialUser({ from: "convex", ctx }); return user; }, }); ```
## Supabase Setup > This setup covers Supabase Row Level Security (RLS) with Hexclave JWTs. It does not sync user data between Supabase and Hexclave. Use Hexclave webhooks if you need data sync. In the Supabase SQL editor, enable Row Level Security for your tables and write policies based on Supabase JWT claims. For example, this sample table demonstrates public rows, authenticated rows, and user-owned rows: ```sql theme={null} CREATE TABLE data ( id bigint PRIMARY KEY, text text NOT NULL, user_id UUID ); INSERT INTO data (id, text, user_id) VALUES (1, 'Everyone can see this', NULL), (2, 'Only authenticated users can see this', NULL), (3, 'Only user with specific id can see this', NULL); ALTER TABLE data ENABLE ROW LEVEL SECURITY; CREATE POLICY "Public read" ON "public"."data" TO public USING (id = 1); CREATE POLICY "Authenticated access" ON "public"."data" TO authenticated USING (id = 2); CREATE POLICY "User access" ON "public"."data" TO authenticated USING (id = 3 AND auth.uid() = user_id); ``` First, follow the instructions on how to get started with Hexclave for your framework in the [Getting Started Guide](https://docs.hexclave.com/guides/getting-started/setup). Create a server action that signs a Supabase JWT using the current Hexclave user ID: ```tsx utils/actions.ts theme={null} 'use server'; import { hexclaveServerApp } from "@/hexclave/server"; import * as jose from "jose"; export const getSupabaseJwt = async () => { const user = await hexclaveServerApp.getUser(); if (!user) { return null; } const token = await new jose.SignJWT({ sub: user.id, role: "authenticated", }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime("1h") .sign(new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET)); return token; }; ``` Create a helper that passes the server-generated JWT to Supabase: ```tsx utils/supabase-client.ts theme={null} import { createBrowserClient } from "@supabase/ssr"; import { getSupabaseJwt } from "./actions"; export const createSupabaseClient = () => { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { accessToken: async () => await getSupabaseJwt() || "" }, ); }; ``` Use the Supabase client from your UI. The RLS policies will decide which rows the user can read based on the Hexclave user ID embedded in the Supabase JWT. ```tsx app/page.tsx theme={null} 'use client'; import { createSupabaseClient } from "@/utils/supabase-client"; import { useHexclaveApp, useUser } from "@hexclave/next"; import { useEffect, useState } from "react"; export default function Page() { const app = useHexclaveApp(); const user = useUser(); const supabase = createSupabaseClient(); const [data, setData] = useState(null); useEffect(() => { supabase.from("data").select().then(({ data }) => setData(data ?? [])); }, []); const listContent = data === null ?

Loading...

: data.length === 0 ?

No notes found

: data.map((note) =>
  • {note.text}
  • ); return (
    {user ? ( <>

    You are signed in

    User ID: {user.id}

    ) : ( )}

    Supabase data

      {listContent}
    ); } ```
    ## CLI Setup Follow these instructions to authenticate users in a command line application with Hexclave. Download the Hexclave CLI authentication template and place it in your project. For Python apps, copy it as `hexclave_cli_template.py`. Example project layout: ```text theme={null} my-python-app/ ├─ main.py └─ hexclave_cli_template.py ``` Import and call `prompt_cli_login`. It opens the browser, lets the user authenticate, and returns a refresh token. The project ID is enough for most projects; only pass `publishable_client_key` if the project has `requirePublishableClientKey` enabled. ```py main.py theme={null} from hexclave_cli_template import prompt_cli_login refresh_token = prompt_cli_login( app_url="https://your-app-url.example.com", project_id="your-project-id-here", ) if refresh_token is None: print("User cancelled the login process. Exiting") exit(1) ``` You can store the refresh token in a local file or keychain and only prompt the user again when no saved refresh token exists. Use the refresh token with Hexclave's REST API to get an access token. ```py theme={null} def get_access_token(refresh_token): access_token_response = hexclave_request( "post", "/api/v1/auth/sessions/current/refresh", headers={ "x-hexclave-refresh-token": refresh_token, }, ) return access_token_response["access_token"] ``` Use the access token to call the Hexclave REST API as the logged-in user. ```py theme={null} def get_user_object(access_token): return hexclave_request( "get", "/api/v1/users/me", headers={ "x-hexclave-access-token": access_token, }, ) user = get_user_object(get_access_token(refresh_token)) print("The user is logged in as", user["display_name"] or user["primary_email"]) ```
    # User Fundamentals Source: https://docs.hexclave.com/guides/getting-started/user-fundamentals Access and manage user information within custom components You've set up Hexclave. Now let's understand the most important object in your application: the **User**. The user object represents whoever is currently interacting with your app — their identity, profile, and metadata. Almost everything you build will revolve around it: retrieving the current user, protecting pages from unauthorized access, updating profile information, signing out, and more. ## Getting the current user On both client and server, you can use the `getUser()` function to get the current user (or `null` if not signed in). In React apps, there is also a `useUser()` hook which automatically updates when the value changes. ```ts my-app.ts theme={null} import { hexclaveClientApp } from "../src/stack/client"; const user = await hexclaveClientApp.getUser(); if (user) { console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "")); } else { console.log("Not signed in"); } ``` ```tsx my-react-component.tsx theme={null} "use client"; import { hexclaveClientApp } from "../src/stack/client"; // Like most `getXyz()` or `listXyz()` functions, Hexclave provides a `useUser()` hook equivalent to `getUser()`. // It behaves the same, but returns the user directly instead of a Promise, and updates when the user object changes. // Note: In Server Components, you can still use `await hexclaveServerApp.getUser()`. export default async function MyReactComponent() { const user = hexclaveClientApp.useUser(); if (user) { return
    Hello, {user.displayName ?? user.primaryEmail ?? "anon"}
    ; } else { return
    You are not logged in
    ; } } ```
    ### Protecting a page & requiring a signed-in user Sometimes, you want to retrieve the user only if they're signed in, and redirect to the sign-in page otherwise. In this case, simply pass `{ or: "redirect" }`, and the function will never return `null`. You can also use `{ or: "throw" }` to throw an error instead — useful in API routes and server actions where a redirect doesn't make sense. In both cases, the return type is non-nullable, so you don't need to handle `null`. ```ts my-app.ts theme={null} import { hexclaveClientApp } from "../src/stack/client"; const user = await hexclaveClientApp.getUser({ or: "redirect" }); // user is guaranteed to be non-null here console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "")); ``` ```tsx my-react-component.tsx theme={null} "use client"; import { hexclaveClientApp } from "../src/stack/client"; export default function MyReactComponent() { const user = hexclaveClientApp.useUser({ or: "redirect" }); // user is guaranteed to be non-null here return
    {`Hello, ${user.displayName ?? user.primaryEmail ?? "anon"}`}
    ; } ```
    ## User data ### What's on a user object? Before diving into updates, here's an overview of the most important fields available on every user: For the full list of fields and methods, see the [User SDK reference](/sdk/types/user). ### Updating a user You can update attributes on a user object with the `user.update()` function. ```ts my-app.ts theme={null} import { hexclaveClientApp } from "../src/stack/client"; const user = await hexclaveClientApp.getUser(); await user.update({ displayName: "New Name" }); ``` ```tsx my-client-component.tsx theme={null} 'use client'; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function MyClientComponent() { const user = useUser(); return ; } ``` ### Custom metadata Beyond built-in fields like `displayName` and `primaryEmail`, you can store your own JSON-like data on a user. Hexclave provides three metadata fields, each with different read and write permissions: | Field | Client access | Server access | Use for | | ------------------------ | -------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `clientMetadata` | Read and write | Read and write | Non-sensitive user preferences, such as theme, locale, onboarding form drafts, or UI state. | | `serverMetadata` | No access | Read and write | Sensitive or internal data that users should not be able to see or modify. | | `clientReadOnlyMetadata` | Read only | Read and write | Server-authoritative data that the client should display, such as subscription plans, role labels, or validated onboarding state. | Use `clientMetadata` for information that is safe for the browser to read and change. For example, a user can store their own theme preference: ```ts my-app.ts theme={null} import { hexclaveClientApp } from "../src/stack/client"; const user = await hexclaveClientApp.getUser(); await user.update({ clientMetadata: { theme: "dark", }, }); ``` ```tsx my-client-component.tsx theme={null} 'use client'; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function ThemeToggle() { const user = useUser({ or: "redirect" }); const currentTheme = user.clientMetadata?.theme ?? "light"; return ( ); } ``` Use `serverMetadata` for sensitive or internal data. It is only available through [`HexclaveServerApp`](/sdk/objects/hexclave-app#hexclaveserverapp): ```ts my-server-file.ts theme={null} import { hexclaveServerApp } from "../src/stack/server"; const user = await hexclaveServerApp.getUser(); await user.update({ serverMetadata: { internalFlag: true, }, }); console.log(user.serverMetadata); ``` Use `clientReadOnlyMetadata` when the client needs to read a value but only your server should decide it. A common example is storing a validated onboarding or subscription state: ```ts my-server-file.ts theme={null} import { hexclaveServerApp } from "../src/stack/server"; const user = await hexclaveServerApp.getUser({ or: "throw" }); await user.update({ clientReadOnlyMetadata: { subscriptionPlan: "premium", onboarded: true, }, }); ``` ```tsx my-client-component.tsx theme={null} 'use client'; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function SubscriptionBadge() { const user = useUser({ or: "redirect" }); const plan = user.clientReadOnlyMetadata?.subscriptionPlan ?? "free"; return
    Current plan: {plan}
    ; } ```
    Do not put secrets or trusted authorization decisions in `clientMetadata`. Users can read and modify it from client-side code. If the value affects access control, validate it on your server and store it in `serverMetadata` or `clientReadOnlyMetadata`. ## Signing out You can sign out the user by calling `user.signOut()`. They will be redirected to the URL [configured as `afterSignOut` in the Hexclave App](/sdk/objects/hexclave-app). ```ts my-app.ts theme={null} import { hexclaveClientApp } from "../src/stack/client"; const user = await hexclaveClientApp.getUser(); await user.signOut(); ``` ```tsx my-client-component.tsx theme={null} 'use client'; import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package export default function MyClientComponent() { const user = useUser(); return ; } ``` ## Example: Custom profile page Hexclave automatically creates a user profile on sign-up. Let's build a page that displays this information. In `app/profile/page.tsx`: ```tsx my-app.tsx theme={null} "use client"; import { useUser, useHexclaveApp, UserButton } from "../src/stack/client"; export default function PageClient() { const user = useUser(); const app = useHexclaveApp(); return (
    {user ? (

    Welcome, {user.displayName ?? "unnamed user"}

    Your e-mail: {user.primaryEmail}

    ) : (

    You are not logged in

    )}
    ); } ``` ```tsx my-client-component.tsx theme={null} "use client"; import { hexclaveClientApp, UserButton } from "../src/stack/client"; export default function MyClientComponent() { const user = hexclaveClientApp.useUser(); const app = hexclaveClientApp.useHexclaveApp(); return (
    {user ? (

    Welcome, {user.displayName ?? "unnamed user"}

    Your e-mail: {user.primaryEmail}

    ) : (

    You are not logged in

    )}
    ); } ```
    After saving your code, you can see the profile page on [http://localhost:3000/profile](http://localhost:3000/profile). For more examples on how to use the `User` object, check the [the SDK documentation](/sdk/types/user). ## Anonymous users Hexclave supports anonymous users - users who can interact with your app without signing up. This is useful for features like guest checkouts, try-before-you-sign-up flows, or collecting analytics before a user creates an account. If you have the analytics app enabled, anonymous users will automatically be created and visible in the Users table. In most ways, anonymous users are just like any other, however, there are some key differences: * Unless opted in, anonymous users cannot access any backend functions that require a signed-in user. * By default, anonymous users are not returned by `getUser()`, `useUser()`. * Anonymous users have a different set of [JWT keys](/guides/apps/authentication/jwts), which means they cannot access protected routes or API endpoints. * Anonymous users are always [restricted](/guides/apps/authentication/restricted-users). To get an anonymous user, pass `{ or: "anonymous" }` to `useUser()` or `getUser()`. This will create an anonymous user if the user isn't signed in. ```ts my-app.ts theme={null} import { hexclaveClientApp } from "../src/stack/client"; const user = await hexclaveClientApp.getUser({ or: "anonymous" }); // user is guaranteed to be non-null here console.log("Signed in: " + (user.displayName ?? user.primaryEmail ?? "")); ``` ```tsx my-client-component.tsx theme={null} "use client"; import { hexclaveClientApp } from "../src/stack/client"; export default function MyComponent() { const user = useUser({ or: "anonymous" }); // user is always non-null — either a real user or an anonymous one return
    Your user ID: {user.id}
    ; } ```
    Anonymous users have an `isAnonymous` property set to `true`. # Hexclave CLI Source: https://docs.hexclave.com/guides/going-further/cli Use the Hexclave CLI to manage config and run admin scripts The Hexclave CLI is published as `@hexclave/cli` and exposes the `stack` command. Use it for branch config workflows and quick admin scripts. ## Install Install the CLI globally to use the `stack` command from any project: ```sh title="Terminal" theme={null} npm install -g @hexclave/cli@latest ``` Then check that it is available: ```sh title="Terminal" theme={null} stack --help ``` You can also run any command without installing globally by prefixing it with `npx @hexclave/cli@latest`, for example: ```sh title="Terminal" theme={null} npx @hexclave/cli@latest project list ``` ## Global options These options can be used before a subcommand: | Option | Description | | ------------------- | -------------------------------------------------------------------------------------------- | | `--project-id ` | Project ID for commands that operate on one project. You can also set `STACK_PROJECT_ID`. | | `--json` | Print JSON output for commands that support it, such as `project list` and `project create`. | | `--version` | Print the CLI version. | The CLI reads Hexclave endpoints from `STACK_API_URL` and `STACK_DASHBOARD_URL`. If unset, it uses Hexclave Cloud. ## Authentication Log in with a browser: ```sh title="Terminal" theme={null} stack login ``` This stores `STACK_CLI_REFRESH_TOKEN` in the CLI credentials file. You can also provide it through the environment, which is useful in CI. ```sh title="Terminal" theme={null} STACK_CLI_REFRESH_TOKEN= stack project list ``` Log out by removing the saved refresh token: ```sh title="Terminal" theme={null} stack logout ``` If you have an anonymous CLI session that should be linked during login, set `STACK_CLI_ANON_REFRESH_TOKEN` before running `login`. ## Project commands List projects owned by the logged-in user: ```sh title="Terminal" theme={null} stack project list ``` Create a project: ```sh title="Terminal" theme={null} stack project create --display-name "My App" ``` Use `--json` when you want machine-readable output: ```sh title="Terminal" theme={null} stack --json project list ``` ## Config commands Pull branch config into a local TypeScript config file: ```sh title="Terminal" theme={null} stack --project-id config pull --config-file ./stack.config.ts ``` By default, `config pull` refuses to overwrite an existing file. Add `--overwrite` when that is intended. Push a local config file to branch config: ```sh title="Terminal" theme={null} stack --project-id config push --config-file ./stack.config.ts ``` `config pull` requires `stack login`. `config push` supports either `stack login` or `STACK_SECRET_SERVER_KEY`. When running in GitHub Actions, `config push` records `GITHUB_REPOSITORY`, `GITHUB_REF_NAME`, `GITHUB_SHA`, and the config file path as the config source when those environment variables are available. ## Execute admin JavaScript `stack exec` runs JavaScript with a preconfigured `HexclaveServerApp` available as `hexclaveServerApp`. ```sh title="Terminal" theme={null} stack --project-id exec "return await hexclaveServerApp.listUsers()" ``` The JavaScript is executed as an async function. Return values are printed as formatted JSON; `undefined` prints nothing. `stack exec` has server-level access to the selected project. It requires `stack login` and intentionally rejects `STACK_SECRET_SERVER_KEY` auth. # hexclave.config.ts Source: https://docs.hexclave.com/guides/going-further/hexclave-config Configure Hexclave from a versioned TypeScript config file. `hexclave.config.ts` is the config file for your Hexclave project. It contains all important settings for your project. The file exports a static `config` object: ```ts title="hexclave.config.ts" theme={null} import type { HexclaveConfig } from "@hexclave/js/config"; export const config: HexclaveConfig = { auth: { allowSignUp: true, password: { allowSignIn: true, }, }, apps: { installed: { authentication: { enabled: true, }, }, }, }; ``` Always import config helpers from the package's lightweight `/config` entrypoint (e.g. `@hexclave/js/config`, `@hexclave/next/config`) rather than the package root. The `/config` entrypoint contains no framework runtime code, so tooling such as the local dashboard can load your config file in a plain Node context. Importing `defineHexclaveConfig` (or the `HexclaveConfig` type) from the package root instead would pull in the entire SDK and fail to load. To get type-checking and editor autocomplete for your config object, wrap it with `defineHexclaveConfig`: ```ts title="hexclave.config.ts" theme={null} import { defineHexclaveConfig } from "@hexclave/js/config"; export const config = defineHexclaveConfig({ auth: { allowSignUp: true, }, }); ``` If you are running Hexclave with a [local dashboard](/guides/going-further/local-vs-cloud-dashboard), you already have a `hexclave.config.ts` file, and any changes you make on the dashboard will automatically be synced to the config file. If you are running Hexclave on a [cloud project](/guides/going-further/local-vs-cloud-dashboard) instead, you may need to use the [CLI's `pull` and `push`](/guides/going-further/cli#config-commands) commands to sync your config file with the cloud. In production, you would usually do this in your GitHub Actions or CI/CD pipeline. ## How To Read This Page Most nested maps use IDs that you choose, such as `payments.products.pro` or `rbac.permissions.admin`. Unless a field says otherwise, custom IDs can contain letters, numbers, underscores, and hyphens, must not start with a hyphen, and can be up to 63 characters long. ## Top-Level Sections | Section | What it controls | | ------------ | ---------------------------------------------------------------------- | | `apps` | Which Hexclave apps are installed and enabled. | | `auth` | Sign-up, sign-in methods, OAuth behavior, and sign-up rules. | | `apiKeys` | Whether user and team API keys are available. | | `rbac` | Custom permissions and permissions granted by default. | | `teams` | Team creation behavior. | | `users` | User self-service account deletion. | | `onboarding` | Requirements that apply during user onboarding. | | `emails` | Email themes and templates. | | `payments` | Products, prices, items, and purchase controls. | | `dbSync` | External database sync targets. | | `dataVault` | Data Vault stores. | | `domains` | Reserved in the config file. Trusted domains are environment-specific. | ## Apps `apps.installed` is a map from app ID to app settings. | Field | Type | Description | | ------------------------ | ---------------------- | ------------------------------------------- | | `apps.installed.` | `{ enabled: boolean }` | Installs an app and enables or disables it. | ```ts title="hexclave.config.ts" theme={null} export const config: HexclaveConfig = { apps: { installed: { authentication: { enabled: true }, teams: { enabled: true }, payments: { enabled: true }, }, }, }; ``` For more information on the major apps you can enable here, see the app docs for [Authentication](/guides/apps/authentication/overview), [Teams](/guides/apps/teams/overview), [RBAC](/guides/apps/rbac/overview), [API Keys](/guides/apps/api-keys/overview), [Emails](/guides/apps/emails/overview), [Payments](/guides/apps/payments/overview), [Data Vault](/guides/apps/data-vault/overview), and [Webhooks](/guides/apps/webhooks/overview). ## Auth These fields control who can create accounts and which sign-in methods are available. | Field | Type | Default | Description | | ------------------ | --------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------- | | `auth.allowSignUp` | `boolean` | `true` | Allows new users to sign up. Server-side user creation can still create users even when this is `false`. | | `auth.password` | `{ allowSignIn?: boolean }` | `{ allowSignIn: false }` | Configures email and password auth. `allowSignIn` allows users to sign in with a password. | | `auth.otp` | `{ allowSignIn?: boolean }` | `{ allowSignIn: false }` | Configures one-time passcode or magic-link style auth. `allowSignIn` allows users to sign in with OTP. | | `auth.passkey` | `{ allowSignIn?: boolean }` | `{ allowSignIn: false }` | Configures passkey auth. `allowSignIn` allows users to sign in with passkeys. | For more information on sign-up, sign-in, and user sessions, see the [Authentication app docs](/guides/apps/authentication/overview) and [User Fundamentals](/guides/getting-started/user-fundamentals). ### OAuth OAuth providers live under `auth.oauth.providers`. | Field | Type | Default | Description | | ----------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `auth.oauth.accountMergeStrategy` | `"link_method"` \| `"raise_error"` \| `"allow_duplicates"` | `"link_method"` | Controls what happens when an OAuth sign-in has the same email as an existing user. | | `auth.oauth.providers.` | `{ type?: OAuth provider ID, allowSignIn?: boolean, allowConnectedAccounts?: boolean }` | `{ allowSignIn: false, allowConnectedAccounts: false }` | Configures one OAuth provider. `type` is the provider implementation, usually the same as ``. `allowSignIn` lets users sign in or sign up with it. `allowConnectedAccounts` lets signed-in users connect it to an existing account. | Valid OAuth provider types are `google`, `github`, `microsoft`, `spotify`, `facebook`, `discord`, `gitlab`, `bitbucket`, `linkedin`, `apple`, `x`, and `twitch`. For provider-specific setup instructions, see [All Auth Providers](/guides/apps/authentication/auth-providers). For account linking behavior, see [Connected Accounts](/guides/apps/authentication/connected-accounts). `accountMergeStrategy` options: | Value | Behavior | | -------------------- | --------------------------------------------------------------------------- | | `"link_method"` | Add the OAuth method to the existing user with the same email. | | `"raise_error"` | Reject the OAuth sign-in when the email already belongs to another account. | | `"allow_duplicates"` | Create a separate user even if another user already has the same email. | ```ts title="hexclave.config.ts" theme={null} export const config: HexclaveConfig = { auth: { oauth: { accountMergeStrategy: "link_method", providers: { google: { type: "google", allowSignIn: true, allowConnectedAccounts: true, }, }, }, }, }; ``` OAuth client IDs, client secrets, custom callback URLs, Apple bundle IDs, Facebook config IDs, and Microsoft tenant IDs are environment-specific and are not configured in `hexclave.config.ts`. ### Sign-Up Rules Sign-up rules are evaluated during sign-up. Higher priority rules run first. The first matching rule decides the outcome. | Field | Type | Default | Description | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `auth.signUpRules.` | `{ enabled?: boolean, displayName?: string, priority?: number, condition?: string, action?: { type: "allow" \| "reject" \| "restrict" \| "log", message?: string } }` | `{ enabled: false, priority: 0, action: { type: "allow" } }` | Defines one rule. `displayName` is shown in the dashboard. `priority` is a non-negative integer; higher values run first. `condition` is the CEL expression. `action.type` is the result when the rule matches, and `action.message` is an internal reject message that is not shown to the user. | | `auth.signUpRulesDefaultAction` | `"allow"` \| `"reject"` | `"allow"` | What to do when no sign-up rule matches. | ```ts title="hexclave.config.ts" theme={null} export const config: HexclaveConfig = { auth: { signUpRulesDefaultAction: "reject", signUpRules: { allowCompanyEmail: { enabled: true, displayName: "Allow company email", priority: 100, condition: 'emailDomain == "example.com"', action: { type: "allow", }, }, }, }, }; ``` See [Sign-up Rules](/guides/apps/authentication/sign-up-rules) for the condition variables and examples. ## RBAC RBAC config defines permissions and default grants. Permission IDs support lowercase letters, numbers, underscores, and colons. System permission IDs may start with `$`. | Field | Type | Description | | ------------------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `rbac.permissions.` | `{ description?: string, scope?: "team" \| "project", containedPermissionIds?: Record }` | Defines one permission. `description` explains it in the dashboard. `scope` decides whether it applies inside a team or globally to the project. `containedPermissionIds` makes it include other permissions, which is useful for roles like `admin`. | | `rbac.defaultPermissions.teamCreator` | `Record` | Permissions granted to users who create a team. | | `rbac.defaultPermissions.teamMember` | `Record` | Permissions granted to users when they become team members. | | `rbac.defaultPermissions.signUp` | `Record` | Project permissions granted to users when they sign up. | ```ts title="hexclave.config.ts" theme={null} export const config: HexclaveConfig = { rbac: { permissions: { admin: { description: "Can manage all team settings", scope: "team", containedPermissionIds: { "team:read": true, "team:write": true, }, }, }, defaultPermissions: { teamCreator: { admin: true, }, }, }, }; ``` See [RBAC Permissions](/guides/apps/rbac/overview) for how to check and grant permissions in code. ## API Keys | Field | Type | Default | Description | | ----------------- | ------------------------------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `apiKeys.enabled` | `{ team?: boolean, user?: boolean }` | `{ team: false, user: false }` | Controls which API key owner types are available. `team` allows team-owned API keys, and `user` allows user-owned API keys. | For more information on creating, listing, and validating API keys, see the [API Keys app docs](/guides/apps/api-keys/overview) and the [`ApiKey` SDK type](/sdk/types/api-key). ## Teams And Users | Field | Type | Default | Description | | ------------ | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `teams` | `{ createPersonalTeamOnSignUp?: boolean, allowClientTeamCreation?: boolean }` | `{ createPersonalTeamOnSignUp: false, allowClientTeamCreation: false }` | Configures team behavior. `createPersonalTeamOnSignUp` creates a personal team for each new user. `allowClientTeamCreation` allows client-side code to create teams. | | `users` | `{ allowClientUserDeletion?: boolean }` | `{ allowClientUserDeletion: false }` | Configures user self-service behavior. `allowClientUserDeletion` allows users to delete their own account from the client. | | `onboarding` | `{ requireEmailVerification?: boolean }` | `{ requireEmailVerification: false }` | Configures onboarding requirements. `requireEmailVerification` requires users to verify their email as part of onboarding. | For more information on team behavior, see [Teams](/guides/apps/teams/overview) and [Team Selection](/guides/apps/teams/team-selection). For user profile, metadata, and onboarding behavior, see [User Fundamentals](/guides/getting-started/user-fundamentals#custom-metadata) and [User Onboarding](/guides/apps/authentication/user-onboarding). ## Emails The config file can define email themes and templates. Email delivery settings, such as SMTP credentials and sender address, are environment-specific and are not configured here. | Field | Type | Default | Description | | ------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emails.selectedThemeId` | `string` | Hexclave default theme ID | The theme used by templates that do not choose their own theme. | | `emails.themes.` | `{ displayName: string, tsxSource: string }` | `{ displayName: "Unnamed Theme", tsxSource: error placeholder }` | Defines one email theme. `displayName` is shown in the dashboard. `tsxSource` is the TSX source for the theme component. Theme IDs must be UUIDs. | | `emails.templates.` | `{ displayName: string, tsxSource: string, themeId?: UUID string \| false }` | `{ displayName: "Unnamed Template", tsxSource: error placeholder }` | Defines one email template. `displayName` is shown in the dashboard. `tsxSource` is the TSX source for the template component. `themeId` chooses a theme; a UUID uses that theme, `false` sends with no theme, and unset uses `emails.selectedThemeId`. Template IDs must be UUIDs. | See [Emails](/guides/apps/emails/overview) for sending emails from your application. ## Payments Payments config is where you define what customers can buy and what entitlements those purchases grant. Supported currency fields are `USD`, `EUR`, `GBP`, `JPY`, `INR`, `AUD`, and `CAD`. All currency/price amounts are **decimal strings** like `"9.99"` or `"1000"` — **not** cent integers or minor-unit numbers. For example, nine dollars and ninety-nine cents is `"9.99"`, not `999`. For more information on products, product lines, prices, items, checkout, and entitlement checks, see the [Payments app docs](/guides/apps/payments/overview). For the SDK shapes returned by entitlement APIs, see the [`Customer` SDK type](/sdk/types/customer) and [`Item` SDK type](/sdk/types/item). In the tables below, `DayInterval` means `[number, "day" | "week" | "month" | "year"]`. ### Global Payment Settings | Field | Type | Default | Description | | ---------------------------- | --------------------------- | ------- | --------------------------------------------------------------------------------- | | `payments.blockNewPurchases` | `boolean` | `false` | Prevents new purchases while keeping existing purchases and subscriptions intact. | | `payments.autoPay` | `{ interval: DayInterval }` | unset | Enables automatic payment behavior on the given interval. | `payments.testMode` is environment-specific and is not configured in `hexclave.config.ts`. ### Product Lines Product lines group mutually exclusive products, such as Free, Pro, and Enterprise plans. For more information on how product lines, add-ons, and switching plans work together, see [Defining products](/guides/apps/payments/overview#defining-products). | Field | Type | Description | | --------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `payments.productLines.` | `{ displayName?: string, customerType: "user" \| "team" \| "custom" }` | Defines one product line. `displayName` is shown to admins and customers. `customerType` decides which kind of customer can own products in this line. | ### Products | Field | Type | Default | Description | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `payments.products.` | `{ displayName?: string, productLineId?: string, customerType: "user" \| "team" \| "custom", freeTrial?: DayInterval, serverOnly?: boolean, stackable?: boolean, isAddOnTo?: false \| Record, prices: Record, includedItems?: Record }` | `{ displayName: product ID, customerType: "user", serverOnly: false, isAddOnTo: false, includedItems: {} }` | Defines one product. `productLineId` places it in a mutually exclusive product line. `customerType` must match the product line when set. `freeTrial` applies to the product. `serverOnly` hides it from client SDK responses. `stackable` allows repeated ownership. `isAddOnTo` requires ownership of one of the listed base products. `prices` defines what can be purchased, and `includedItems` defines granted entitlements. | ### Prices Each price must include at least one supported currency. Currency amounts are decimal strings in `""` or `"."` format (e.g. `"9.99"`, `"0.01"`, `"1000"`). Do **not** use cent integers — to represent \$9.99, write `"9.99"`, not `999`. | Field | Type | Default | Description | | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `payments.products..prices.` | `{ USD?: string, EUR?: string, GBP?: string, JPY?: string, INR?: string, AUD?: string, CAD?: string, interval?: DayInterval, serverOnly?: boolean, freeTrial?: DayInterval }` | `{ serverOnly: false }` | Defines one price. Each currency value is a decimal string like `"9.99"` (not cents). Include at least one currency amount. `interval` makes it recurring; leave it unset for a one-time price. `serverOnly` hides the price from client SDK responses. `freeTrial` applies to this specific price. JPY amounts cannot have decimals. | ### Included Items And Standalone Items Items are quantifiable entitlements, such as credits, seats, messages, or API calls. For more information on item balances and consuming entitlements in your app, see [Checking item balances](/guides/apps/payments/overview#checking-item-balances). | Field | Type | Default | Description | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `payments.products..includedItems.` | `{ quantity: number, repeat?: DayInterval \| "never", expires?: "never" \| "when-purchase-expires" \| "when-repeated" }` | `{ quantity: 0, repeat: "never", expires: "when-repeated" }` | Defines one item grant from this product. `quantity` is the amount granted. `repeat` controls whether the grant repeats. `expires` controls when granted items expire. | | `payments.items.` | `{ displayName?: string, customerType?: "user" \| "team" \| "custom" }` | `{ displayName: item ID, customerType: "user" }` | Defines a standalone item. `displayName` is shown for the item. `customerType` decides which kind of customer can hold it. | ```ts title="hexclave.config.ts" theme={null} export const config: HexclaveConfig = { payments: { productLines: { plans: { displayName: "Plans", customerType: "user", }, }, products: { pro: { displayName: "Pro", productLineId: "plans", customerType: "user", prices: { monthly: { USD: "19.00", interval: [1, "month"], }, }, includedItems: { credits: { quantity: 1000, repeat: [1, "month"], expires: "when-repeated", }, }, }, }, items: { credits: { displayName: "Credits", customerType: "user", }, }, }, }; ``` See [Payments](/guides/apps/payments/overview) for checkout and entitlement usage. ## DB Sync `dbSync.externalDatabases` defines external databases that Hexclave can sync to. For more information on connecting Hexclave with your own backend and database workflows, see [Setup](/guides/getting-started/setup) and the [REST API overview](/api/overview). | Field | Type | Description | | ----------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------- | | `dbSync.externalDatabases.` | `{ type: "postgres", connectionString: string }` | Defines one external Postgres database. `connectionString` is required when `type` is `"postgres"`. | A database connection string is sensitive. Only put it in `hexclave.config.ts` if that file is kept private and reviewed like other secrets. ## Data Vault | Field | Type | Default | Description | | ---------------------------- | -------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- | | `dataVault.stores.` | `{ displayName?: string }` | `{ displayName: "Unnamed Vault" }` | Defines one Data Vault store. `displayName` is the name shown for the store. | For more information on storing sensitive user data, see the [Data Vault app docs](/guides/apps/data-vault/overview). ## Domains `domains` is reserved in the config file and currently has no file-level attributes. Trusted domains, localhost allowance, and handler paths are environment-specific settings. For more information on production readiness and domain-related launch checks, see the [Launch Checklist](/guides/apps/launch-checklist/overview). # Local vs. Cloud Dashboard Source: https://docs.hexclave.com/guides/going-further/local-vs-cloud-dashboard Understand when to use a Hexclave development environment and when to use the hosted cloud dashboard. Hexclave has two common ways to work with a project: | Option | Best for | How it is configured | | --------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | | Local dashboard | Development | A local `hexclave.config.ts` file plus `hexclave dev --config-file ...`. | | Cloud dashboard | Production | A hosted project on [app.hexclave.com](https://app.hexclave.com) with project ID and keys in your app environment. | ## Development Environment A development environment starts Hexclave for the project you are currently building. It is the recommended default while integrating Hexclave because your project config can live next to your app code in `hexclave.config.ts`. Use a development environment when you want to: * Keep app setup, auth settings, RBAC permissions, email templates, payment products, and similar config in source control. * Let teammates review config changes in pull requests. * Try Hexclave apps before creating or connecting a cloud project. * Run your app with environment variables provided by the Hexclave CLI. The usual setup looks like this: ```ts title="hexclave.config.ts" theme={null} import type { HexclaveConfig } from "@hexclave/js/config"; export const config: HexclaveConfig = "show-onboarding"; ``` ```json title="package.json" theme={null} { "scripts": { "dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:inner", "dev:inner": "" } } ``` For the full config file reference, see [`hexclave.config.ts`](/guides/going-further/hexclave-config). For CLI details, see [Hexclave CLI](/guides/going-further/cli). ## Cloud Dashboard The cloud dashboard is the hosted Hexclave app at [app.hexclave.com](https://app.hexclave.com). Use it for projects that should live in Hexclave Cloud, especially production projects and projects shared with a team. Use the cloud dashboard when you want to: * Manage a real Hexclave Cloud project. * Generate production project keys. * Configure environment-specific settings that should not live in `hexclave.config.ts`, such as secrets, sender credentials, trusted domains, and payment test mode. * Let non-developers manage project settings through the hosted dashboard. For a frontend-only app, connect to a cloud project with the project ID: ```env title=".env.local" theme={null} HEXCLAVE_PROJECT_ID= ``` For a backend, or an app that has both frontend and backend code, also add a secret server key: ```env title=".env.local" theme={null} HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` You can get these values from the cloud dashboard. The project ID appears in the project URL, and server keys are generated from the Project Keys page. ## Moving Between Them To start with a development environment, run the setup wizard and choose the config-file flow: ```sh title="Terminal" theme={null} stack init --mode create ``` If you already have a config file, link it instead: ```sh title="Terminal" theme={null} stack init --mode link-config --config-file ./hexclave.config.ts ``` To start with a cloud project, create one from the CLI: ```sh title="Terminal" theme={null} stack init --mode create-cloud ``` Or link an existing cloud project: ```sh title="Terminal" theme={null} stack init --mode link-cloud --select-project-id ``` You can also copy config between the two styles. Pull cloud branch config into a local config file: ```sh title="Terminal" theme={null} stack --project-id config pull --config-file ./hexclave.config.ts ``` Push local config-file changes back to a cloud project: ```sh title="Terminal" theme={null} stack --project-id config push --config-file ./hexclave.config.ts ``` `config pull` requires `stack login`. `config push` supports either `stack login` or `HEXCLAVE_SECRET_SERVER_KEY`. For the full setup flow by framework, see [Setup](/guides/getting-started/setup). # Convex Source: https://docs.hexclave.com/guides/integrations/convex/overview Integrate Hexclave with Convex This guide shows how to integrate Hexclave with Convex. ### 1) Create a Convex + Next.js app First, initialize a new Convex + Next.js app: ```bash title="Terminal" theme={null} npm create convex@latest # make sure to choose "Next.js" and "No auth" when asked — we will add auth later ``` Then, run `npx convex dev` to start the Convex backend, and `npm run dev` to start the development server. ### 2) Install Hexclave Next, open your app directory and follow the [Hexclave setup prompt](/guides/getting-started/setup). Choose **Next.js** and **Convex** in the setup builder, then copy the generated prompt into your coding agent. ### 3) Create a Hexclave project [Create a new Hexclave project](https://app.hexclave.com). Get the project ID & API key environment variables from the dashboard. * Set the `.env.local` file to the environment variables. * In Convex, go to the dashboard of your project's deployment, and also set the environment variables there. ### 4) Update code Next, update or create a file in `convex/auth.config.ts`: ```ts theme={null} import { getConvexProvidersConfig } from "@hexclave/js"; // Vanilla JS // or: import { getConvexProvidersConfig } from "@hexclave/react"; // React // or: import { getConvexProvidersConfig } from "@hexclave/next"; // Next.js export default { providers: getConvexProvidersConfig({ projectId: process.env.HEXCLAVE_PROJECT_ID, // or: process.env.NEXT_PUBLIC_HEXCLAVE_PROJECT_ID }), } ``` Then, update your Convex client to use Hexclave: ```ts theme={null} convexClient.setAuth(hexclaveClientApp.getConvexClientAuth({})); // browser JS convexReactClient.setAuth(hexclaveClientApp.getConvexClientAuth({})); // React convexHttpClient.setAuth(hexclaveClientApp.getConvexHttpClientAuth({ tokenStore: requestObject })); // HTTP, see Hexclave docs for more information on tokenStore ``` ### 5) Done! Done! Now, you'll be able to access Hexclave's functionality from your frontend & backend: ```ts theme={null} // MyPage.tsx export function MyPage() { // see https://docs.hexclave.com for more information on how to use Hexclave const user = useUser(); return
    Your email is {user.email}
    ; } // myFunctions.ts export const myQuery = query({ handler: async (ctx, args) => { // In queries & mutations, use the special `getPartialUser` function to get user info const obj = await hexclaveServerApp.getPartialUser({ from: "convex", ctx }); return JSON.stringify(obj); }, }); ``` You can find the production-ready template with Stack-Auth, Convex & Shadcn pre-configured [here on GitHub](https://github.com/developing-gamer/next-convex-stack-template). # Supabase Source: https://docs.hexclave.com/guides/integrations/supabase/overview Integrate Hexclave with Supabase RLS This guide shows how to integrate Hexclave with Supabase row level security (RLS). This guide only focuses on the RLS/JWT integration and does not sync user data between Supabase and Stack. You should use [webhooks](/guides/apps/webhooks/overview) to achieve data sync. ## Setup Let's create a sample table and some RLS policies to demonstrate how to integrate Hexclave with Supabase RLS. You can apply the same logic to your own tables and policies. First, let's create a Supabase project, then go to the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new) and create a new table with some sample data and RLS policies. ```sql title="Supabase SQL Editor" theme={null} -- Create the 'data' table CREATE TABLE data ( id bigint PRIMARY KEY, text text NOT NULL, user_id UUID ); -- Insert sample data INSERT INTO data (id, text, user_id) VALUES (1, 'Everyone can see this', NULL), (2, 'Only authenticated users can see this', NULL), (3, 'Only user with specific id can see this', NULL); -- Enable Row Level Security ALTER TABLE data ENABLE ROW LEVEL SECURITY; -- Allow everyone to read the first row CREATE POLICY "Public read" ON "public"."data" TO public USING (id = 1); -- Allow authenticated users to read the second row CREATE POLICY "Authenticated access" ON "public"."data" TO authenticated USING (id = 2); -- Allow only the owner of the row to read it CREATE POLICY "User access" ON "public"."data" TO authenticated USING (id = 3 AND auth.uid() = user_id); ``` Now let's create a new Next.js project and install Hexclave and Supabase client. (more details on [Next.js setup](https://nextjs.org/docs/getting-started/installation), [Hexclave setup](/guides/getting-started/setup), and [Supabase setup](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs)) ```bash title="Terminal" theme={null} npx create-next-app@latest -e with-supabase stack-supabase cd stack-supabase ``` Then follow the [Hexclave setup prompt](/guides/getting-started/setup). Choose **Next.js** and **Supabase** in the setup builder, then copy the generated prompt into your coding agent. Now copy the environment variables from the Supabase dashboard to the `.env.local` file: * `NEXT_PUBLIC_SUPABASE_URL` * `NEXT_PUBLIC_SUPABASE_ANON_KEY` * `SUPABASE_JWT_SECRET` Copy environment variables from the Hexclave dashboard to the `.env.local` file. * `NEXT_PUBLIC_HEXCLAVE_PROJECT_ID` * `NEXT_PUBLIC_HEXCLAVE_PUBLISHABLE_CLIENT_KEY` * `HEXCLAVE_SECRET_SERVER_KEY` Now let's create a server action that mints a supabase JWT with the Hexclave user ID if the user is authenticated. ```tsx title="/utils/actions.ts" theme={null} 'use server'; import { hexclaveServerApp } from "@/hexclave/server"; import * as jose from "jose"; export const getSupabaseJwt = async () => { const user = await hexclaveServerApp.getUser(); if (!user) { return null; } const token = await new jose.SignJWT({ sub: user.id, role: "authenticated", }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime('1h') .sign(new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET)); return token; }; ``` And now create a helper function to create a Supabase client with the JWT signed by the server action ```tsx title="/utils/supabase-client.ts" theme={null} import { createBrowserClient } from "@supabase/ssr"; import { getSupabaseJwt } from "./actions"; export const createSupabaseClient = () => { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { accessToken: async () => await getSupabaseJwt() || "" } ); } ``` Let's create an example page that fetches data from Supabase and displays it. ```tsx title="/app/page.tsx" theme={null} 'use client'; import { createSupabaseClient } from "@/utils/supabase-client"; import { useHexclaveApp, useUser } from "@hexclave/next"; import { useEffect, useState } from "react"; export default function Page() { const app = useHexclaveApp(); const user = useUser(); const supabase = createSupabaseClient(); const [data, setData] = useState(null); useEffect(() => { supabase.from("data").select().then(({ data }) => setData(data ?? [])); }, []); const listContent = data === null ?

    Loading...

    : data.length === 0 ?

    No notes found

    : data.map((note) =>
  • {note.text}
  • ); return (
    { user ? <>

    You are signed in

    User ID: {user.id}

    : }

    Supabase data

      {listContent}
    ) } ``` Now you should be able to compare the data you can view with an anonymous user, an authenticated user. You can also add your user Id to the row 3 of the Supabase table, and you should be able to see the row if and only if you are signed in with that user.
    You can find the full example [here on GitHub](https://github.com/hexclave/hexclave/tree/main/examples/supabase). # TanStack Start Source: https://docs.hexclave.com/guides/integrations/tanstack-start/overview Add Hexclave to a TanStack Start app. The `@hexclave/tanstack-start` package is currently alpha. Pin exact package versions before shipping production apps. TanStack Start is a full-stack React framework built on TanStack Router and Vite. Hexclave's TanStack Start package provides the same auth components and hooks as the React SDK, with cookie handling wired for TanStack Start. ## Setup If you do not have a TanStack Start app yet, create one with the TanStack CLI: ```bash title="Terminal" theme={null} npx @tanstack/cli@latest create ``` TanStack also publishes official examples if you prefer to start from a working project. Install the alpha TanStack Start package: ```bash title="Terminal" theme={null} npm install @hexclave/tanstack-start ``` In the [Hexclave dashboard](https://app.hexclave.com/projects), create a project and add these variables to your TanStack Start environment: ```bash title=".env" theme={null} VITE_HEXCLAVE_PROJECT_ID= HEXCLAVE_SECRET_SERVER_KEY= ``` Keep `HEXCLAVE_SECRET_SERVER_KEY` server-only. Do not expose it to client code. Create a Stack client app with cookie storage: ```ts title="src/stack/client.ts" theme={null} import { HexclaveClientApp } from "@hexclave/tanstack-start"; export const hexclaveClientApp = new HexclaveClientApp({ projectId: import.meta.env.VITE_HEXCLAVE_PROJECT_ID, tokenStore: "cookie", redirectMethod: "window", }); ``` Add `HexclaveProvider` and `HexclaveTheme` around your route outlet: ```tsx title="src/routes/__root.tsx" theme={null} import { HexclaveProvider, HexclaveTheme } from "@hexclave/tanstack-start"; import { createRootRoute, HeadContent, Outlet, Scripts } from "@tanstack/react-router"; import { hexclaveClientApp } from "../stack/client"; export const Route = createRootRoute({ component: RootComponent, shellComponent: RootDocument, }); function RootComponent() { return ( ); } function RootDocument({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Create a splat route at `/handler/*` for Hexclave's built-in pages: ```tsx title="src/routes/handler/$.tsx" theme={null} import { HexclaveHandler } from "@hexclave/tanstack-start"; import { createFileRoute, useLocation } from "@tanstack/react-router"; export const Route = createFileRoute("/handler/$")({ ssr: false, component: HandlerPage, }); function HandlerPage() { const { pathname } = useLocation(); return ; } ``` Use Stack hooks from inside components rendered under the provider: ```tsx title="src/routes/index.tsx" theme={null} import { useUser } from "@hexclave/tanstack-start"; import { createFileRoute, Link } from "@tanstack/react-router"; export const Route = createFileRoute("/")({ component: HomePage, }); function HomePage() { const user = useUser(); if (!user) { return Sign in; } return
    Signed in as {user.primaryEmail}
    ; } ``` Routes that use browser redirects should render on the client: ```tsx title="src/routes/protected.tsx" theme={null} import { useUser } from "@hexclave/tanstack-start"; import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/protected")({ ssr: false, component: ProtectedPage, }); function ProtectedPage() { const user = useUser({ or: "redirect" }); return
    Welcome, {user.primaryEmail}
    ; } ```
    ## Notes * The handler route must stay under the same origin as your app when using `tokenStore: "cookie"`. * Render the handler route on the client (`ssr: false`) because built-in auth pages read browser location state. * Render routes that rely on `useUser({ or: "redirect" })` on the client (`ssr: false`) when using `redirectMethod: "window"`. * Use `redirectMethod: "window"` unless you explicitly wire a TanStack Router navigation adapter. * If you change auth routes, configure the matching `urls` on `HexclaveClientApp`. * For server-only logic, use TanStack Start server functions and keep `HEXCLAVE_SECRET_SERVER_KEY` out of client modules. For TanStack Start framework details, see the [TanStack Start quick start](https://tanstack.com/start/latest/docs/framework/react/quick-start) and [server functions guide](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions). # Vercel Source: https://docs.hexclave.com/guides/integrations/vercel/overview Deploy your Hexclave project on Vercel with the dashboard integration flow. This guide mirrors the Vercel integration flow in the Hexclave dashboard app. ## What this integration covers * Generate Hexclave keys for your project * Add the required environment variables in Vercel * Redeploy and verify the auth flow ## Setup In the Vercel dashboard, open the project you want to connect to Hexclave. [Open Vercel dashboard](https://vercel.com/dashboard) In your Hexclave dashboard, open the **Vercel Integration** app and generate keys for your project. This produces a project ID plus API keys that you can paste into Vercel. In Vercel, go to **Project -> Settings -> Environment Variables** and add: * `NEXT_PUBLIC_HEXCLAVE_PROJECT_ID` * `NEXT_PUBLIC_HEXCLAVE_PUBLISHABLE_CLIENT_KEY` * `HEXCLAVE_SECRET_SERVER_KEY` Add `NEXT_PUBLIC_HEXCLAVE_API_URL` only if you are not using the default hosted Hexclave API. Trigger a new deployment so Vercel picks up the new environment variables. If you use both preview and production environments, make sure both are updated. Open your deployed app and test authentication. A quick check is to visit `/handler/signup` and confirm the sign-up flow loads correctly. ## Notes * Keys are sensitive. Store server keys only in server-side environment variables. * If authentication fails after setup, confirm variable names are exact and redeploy again. For additional Vercel details, see [Vercel environment variable docs](https://vercel.com/docs/environment-variables). # Known Errors Source: https://docs.hexclave.com/guides/other/known-errors Reference for Hexclave known errors exposed by public TypeScript SDK types Hexclave uses known errors for expected, actionable failures. Some public SDK methods return `Result<..., KnownErrors["..."]>` in their TypeScript signatures. This page lists only the known errors currently exposed through those public TypeScript types. Hexclave has additional internal known errors for API implementation details. They are intentionally not part of this public SDK reference. At runtime, known errors include a stable `errorCode` value. In the REST API, the same value appears as the JSON `code` field and the `X-Stack-Known-Error` response header. ```json theme={null} { "code": "EMAIL_PASSWORD_MISMATCH", "error": "Wrong e-mail or password." } ``` ## Handling known errors When using SDK methods that return `Result`, check `result.status` and inspect `result.error`. ```ts theme={null} const result = await hexclaveClientApp.signInWithCredential({ email, password, }); if (result.status === "error") { console.log(result.error.errorCode); } ``` Check specific errors by comparing `error.errorCode`: ```ts theme={null} if (result.error.errorCode === "EMAIL_PASSWORD_MISMATCH") { // Show "wrong email or password" UI. } ``` For REST API calls, read the `code` field from the JSON response body. Error messages are human-readable and may include request-specific details. Use `code` for programmatic handling. ## Exposed known errors | Public TypeScript type | Runtime `errorCode` | Returned by | | ----------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `KnownErrors["EmailPasswordMismatch"]` | `EMAIL_PASSWORD_MISMATCH` | `hexclaveClientApp.signInWithCredential()` | | `KnownErrors["InvalidTotpCode"]` | `INVALID_TOTP_CODE` | `hexclaveClientApp.signInWithCredential()`, `hexclaveClientApp.signInWithMagicLink()`, `hexclaveClientApp.signInWithMfa()`, `hexclaveClientApp.signInWithPasskey()` | | `KnownErrors["UserWithEmailAlreadyExists"]` | `USER_EMAIL_ALREADY_EXISTS` | `hexclaveClientApp.signUpWithCredential()` | | `KnownErrors["PasswordRequirementsNotMet"]` | `PASSWORD_REQUIREMENTS_NOT_MET` | `hexclaveClientApp.signUpWithCredential()`, `user.updatePassword()`, `user.setPassword()` | | `KnownErrors["BotChallengeFailed"]` | `BOT_CHALLENGE_FAILED` | `hexclaveClientApp.signUpWithCredential()`, `hexclaveClientApp.sendMagicLinkEmail()` | | `KnownErrors["PasskeyAuthenticationFailed"]` | `PASSKEY_AUTHENTICATION_FAILED` | `hexclaveClientApp.signInWithPasskey()` | | `KnownErrors["PasskeyRegistrationFailed"]` | `PASSKEY_REGISTRATION_FAILED` | `user.registerPasskey()` | | `KnownErrors["PasskeyWebAuthnError"]` | `PASSKEY_WEBAUTHN_ERROR` | `hexclaveClientApp.signInWithPasskey()`, `user.registerPasskey()` | | `KnownErrors["CliAuthError"]` | `CLI_AUTH_ERROR` | `hexclaveClientApp.promptCliLogin()` | | `KnownErrors["CliAuthExpiredError"]` | `CLI_AUTH_EXPIRED_ERROR` | `hexclaveClientApp.promptCliLogin()` | | `KnownErrors["CliAuthUsedError"]` | `CLI_AUTH_USED_ERROR` | `hexclaveClientApp.promptCliLogin()` | | `KnownErrors["UserNotFound"]` | `USER_NOT_FOUND` | `hexclaveClientApp.sendForgotPasswordEmail()` | | `KnownErrors["RedirectUrlNotWhitelisted"]` | `REDIRECT_URL_NOT_WHITELISTED` | `hexclaveClientApp.sendMagicLinkEmail()` | | `KnownErrors["VerificationCodeError"]` | `VERIFICATION_ERROR` | `hexclaveClientApp.resetPassword()`, `hexclaveClientApp.verifyPasswordResetCode()`, `hexclaveClientApp.verifyTeamInvitationCode()`, `hexclaveClientApp.acceptTeamInvitation()`, `hexclaveClientApp.getTeamInvitationDetails()`, `hexclaveClientApp.verifyEmail()`, `hexclaveClientApp.signInWithMagicLink()`, `hexclaveClientApp.signInWithMfa()` | | `KnownErrors["OAuthAccessTokenNotAvailable"]` | `OAUTH_ACCESS_TOKEN_NOT_AVAILABLE` | `connectedAccount.getAccessToken()`, `connectedAccount.useAccessToken()` | | `KnownErrors["OAuthProviderAccountIdAlreadyUsedForSignIn"]` | `OAUTH_PROVIDER_ACCOUNT_ID_ALREADY_USED_FOR_SIGN_IN` | `oauthProvider.update()`, `serverOAuthProvider.update()`, `hexclaveServerApp.createOAuthProvider()` | | `KnownErrors["EmailAlreadyVerified"]` | `EMAIL_ALREADY_VERIFIED` | `user.sendVerificationEmail()` | | `KnownErrors["PasswordConfirmationMismatch"]` | `PASSWORD_CONFIRMATION_MISMATCH` | `user.updatePassword()` | ## Parent error types Some exposed types are parent classes for more specific errors. For example: * `KnownErrors["PasswordRequirementsNotMet"]` may currently have `PASSWORD_TOO_SHORT` or `PASSWORD_TOO_LONG` as the runtime `errorCode`. * `KnownErrors["VerificationCodeError"]` may currently have `VERIFICATION_CODE_NOT_FOUND`, `VERIFICATION_CODE_EXPIRED`, `VERIFICATION_CODE_ALREADY_USED`, or `VERIFICATION_CODE_MAX_ATTEMPTS_REACHED` as the runtime `errorCode`. When you only need broad handling, check the parent type returned in TypeScript. When you need precise UI copy, compare `error.errorCode` against the specific runtime code. # Self-host Source: https://docs.hexclave.com/guides/other/self-host Deploy Hexclave on your own infrastructure with full control over your authentication system. **If you self-host, YOU will be responsible for updating Hexclave and its dependencies.** Security patches, bug fixes, and new features require manual updates on your infrastructure. If you would like premium support to help with this, [contact us](mailto:team@hexclave.com). Hexclave is fully open-source and can be self-hosted on your own infrastructure. The supported production path is the `stackauth/server` Docker image, which runs the Hexclave API backend and dashboard in one container. If you are unsure whether you should self-host, here are some things to consider: * **Complexity**: Hexclave is a complex project with many interdependent services. Self-hosting requires managing these services and ensuring they work together seamlessly. * **Updates**: Hexclave is a rapidly evolving project with frequent feature and fix releases. Self-hosting requires you to manage updates and apply them timely. * **Reliability**: Self-hosting requires you to ensure the reliability of your infrastructure. Downtimes and outages can be costly to handle. * **Security**: Self-hosting requires ensuring the security of your infrastructure. A compromised service can affect your users. For most users, we recommend using [Hexclave's cloud hosted solution](https://app.hexclave.com). However, if you understand the above challenges and are comfortable managing them, follow the instructions below to self-host! ## What You Run In production, plan for these components: * **Hexclave server**: The Docker image that serves the API backend and dashboard. The API is what your application SDKs call. The dashboard is where you manage projects, users, auth methods, and app settings. * **Postgres**: Required. Stores Hexclave data and is migrated by the server image on startup unless you disable migrations. * **Cron scheduler**: Required for production. Calls internal maintenance endpoints for email queue processing and database sync jobs. * **Reverse proxy or load balancer**: Required for a production deployment. Terminate HTTPS and route traffic to the API and dashboard ports. * **Email provider**: Required for production email flows such as magic links, verification, password reset, and invitations. Configure SMTP or use a provider integration from the dashboard. * **Svix**: Required only if you use webhooks. You can use Svix Cloud or self-host Svix. * **S3-compatible storage**: Required for features that store files or assets. * **ClickHouse**: Required for the supported Docker deployment path. The migration script creates ClickHouse databases, tables, views, users, grants, and row policies after Postgres migrations. * **Freestyle and OpenRouter keys**: Freestyle is required for custom, manual, or programmatic email sending. OpenRouter is optional and used by AI-assisted dashboard features. For local app development, use [Local Development](/guides/going-further/local-development) or the [Local Emulator](/guides/going-further/local-emulator). Do not use the production Docker guide as your day-to-day local development setup. ## Deploy With Docker ### 1. Create Postgres and ClickHouse Use a managed Postgres service for production. The server reads its database URL from `STACK_DATABASE_CONNECTION_STRING`. Use a managed ClickHouse service or your own ClickHouse cluster. The server reads its ClickHouse URL and credentials from `STACK_CLICKHOUSE_URL`, `STACK_CLICKHOUSE_ADMIN_USER`, `STACK_CLICKHOUSE_ADMIN_PASSWORD`, and `STACK_CLICKHOUSE_EXTERNAL_PASSWORD`, and the database name from `STACK_CLICKHOUSE_DATABASE` (defaults to `default` when unset—set it to match the database you use in ClickHouse). The ClickHouse admin user must be able to create databases, tables, views, users, grants, and row policies. The migration script creates the `analytics_internal` database, views in the configured database, and a limited external user used by analytics queries. For a quick non-production smoke test, you can run both databases locally: ```bash title="Terminal" theme={null} docker network create hexclave docker run -d \ --name hexclave-postgres \ --network hexclave \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=password \ -e POSTGRES_DB=stackframe \ -p 5432:5432 \ postgres:latest docker run -d \ --name hexclave-clickhouse \ --network hexclave \ -e CLICKHOUSE_DB=analytics \ -e CLICKHOUSE_USER=stackframe \ -e CLICKHOUSE_PASSWORD=password \ -e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 \ -p 8123:8123 \ clickhouse/clickhouse-server:25.10 ``` Set `STACK_CLICKHOUSE_DATABASE` to the same logical database your ClickHouse server uses (for this smoke test, `CLICKHOUSE_DB=analytics` and `STACK_CLICKHOUSE_DATABASE=analytics`). If you omit `STACK_CLICKHOUSE_DATABASE`, the backend defaults to `default` and will not match a container created only with `CLICKHOUSE_DB=analytics`. Do not use the example passwords, open ports, or single-node database layout for production. ### 2. Create an Environment File Start from the [server environment template](https://github.com/hexclave/hexclave/blob/dev/docker/server/.env), then fill in your production values. At minimum, set: ```env title="hexclave.env" theme={null} NEXT_PUBLIC_STACK_API_URL=https://auth-api.example.com NEXT_PUBLIC_STACK_DASHBOARD_URL=https://auth.example.com STACK_DATABASE_CONNECTION_STRING=postgresql://postgres:password@hexclave-postgres:5432/stackframe STACK_SERVER_SECRET=replace-with-a-32-byte-base64url-secret CRON_SECRET=replace-with-a-long-random-secret STACK_CLICKHOUSE_URL=http://hexclave-clickhouse:8123 STACK_CLICKHOUSE_DATABASE=analytics STACK_CLICKHOUSE_ADMIN_USER=stackframe STACK_CLICKHOUSE_ADMIN_PASSWORD=password STACK_CLICKHOUSE_EXTERNAL_PASSWORD=replace-with-a-long-random-password STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=replace-with-a-random-value STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=replace-with-a-random-value STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=replace-with-a-random-value STACK_SEED_INTERNAL_PROJECT_USER_EMAIL=admin@example.com STACK_SEED_INTERNAL_PROJECT_USER_PASSWORD=replace-with-a-long-random-password STACK_SEED_INTERNAL_PROJECT_USER_INTERNAL_ACCESS=true STACK_SEED_INTERNAL_PROJECT_SIGN_UP_ENABLED=false ``` Generate `STACK_SERVER_SECRET` with a stable, high-entropy value and keep it unchanged across deploys: ```bash title="Terminal" theme={null} openssl rand -base64 32 | tr '+/' '-_' | tr -d '=' ``` Generate the three `STACK_SEED_INTERNAL_PROJECT_*_KEY` values with any stable random values, for example: ```bash title="Terminal" theme={null} openssl rand -hex 32 ``` The Docker entrypoint can generate the internal project keys if they are missing, but setting stable values yourself avoids rotating the dashboard project's keys on every fresh container start. Generate `CRON_SECRET` with a stable random value too. Your scheduler uses it to authenticate internal maintenance requests: ```bash title="Terminal" theme={null} openssl rand -hex 32 ``` ### 3. Run the Server Run the Docker image with the environment file: ```bash title="Terminal" theme={null} docker run -d \ --name hexclave \ --network hexclave \ --env-file hexclave.env \ -p 8101:8101 \ -p 8102:8102 \ stackauth/server:latest ``` The container starts two services: | Service | Container port | Purpose | | ----------- | -------------- | ----------------------------------------------- | | Dashboard | `8101` | Admin dashboard for Hexclave projects | | API backend | `8102` | API used by the dashboard and your applications | On startup, the image runs database migrations and the seed script by default. To separate migrations from application startup, run one deployment with `STACK_RUN_MIGRATIONS=true` and `STACK_RUN_SEED_SCRIPT=true`, then run steady-state application containers with: ```env theme={null} STACK_RUN_MIGRATIONS=false STACK_RUN_SEED_SCRIPT=false ``` Do this only after migrations and seeding have completed successfully for the current image version. ### 4. Run Cron Jobs The Docker image does not start cron jobs for you. In production, configure exactly one scheduler for each deployment environment to call these internal endpoints with the `CRON_SECRET` bearer token: | Endpoint | Purpose | | ------------------------------------------------- | ----------------------------------------------- | | `/api/latest/internal/email-queue-step` | Processes queued emails. | | `/api/latest/internal/external-db-sync/sequencer` | Schedules external database sync work. | | `/api/latest/internal/external-db-sync/poller` | Polls and advances external database sync work. | For example, a scheduler can run these requests every minute: ```bash title="Scheduler" theme={null} curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://auth-api.example.com/api/latest/internal/email-queue-step curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://auth-api.example.com/api/latest/internal/external-db-sync/sequencer curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://auth-api.example.com/api/latest/internal/external-db-sync/poller ``` Use your deployment platform's scheduler, Kubernetes `CronJob`, systemd timer, or another reliable cron service. If you run multiple application replicas, do not let every replica run its own scheduler against the same database. ### 5. Put It Behind HTTPS Expose the dashboard and API through HTTPS with your reverse proxy or load balancer: | Public URL | Proxies to | | ------------------------------ | ----------------------- | | `https://auth.example.com` | dashboard port `8101` | | `https://auth-api.example.com` | API backend port `8102` | The public URLs must match `NEXT_PUBLIC_STACK_DASHBOARD_URL` and `NEXT_PUBLIC_STACK_API_URL`. The API URL must be reachable from browsers, your application servers, and the dashboard. Keep `NEXT_PUBLIC_STACK_API_URL` as the browser-reachable API URL. In the bundled Docker image, the entrypoint sets the dashboard's server-side API URL to the backend inside the same container, so most deployments should not set API split variables manually. If you run the API and dashboard as separate services outside the bundled image, the codebase also supports `NEXT_PUBLIC_BROWSER_STACK_API_URL` and `NEXT_PUBLIC_SERVER_STACK_API_URL` for advanced network layouts. ### 6. Sign In to the Dashboard Open your dashboard URL and sign in with the seeded admin user from `STACK_SEED_INTERNAL_PROJECT_USER_EMAIL` and `STACK_SEED_INTERNAL_PROJECT_USER_PASSWORD`. After you have access, create a project for your application and follow the [setup guide](/guides/getting-started/setup). For self-hosted projects, your app must also point the SDK at your API URL: ```env title=".env.local" theme={null} NEXT_PUBLIC_STACK_API_URL=https://auth-api.example.com STACK_API_URL=https://auth-api.example.com ``` Keep using the project ID, publishable client key, and secret server key shown in your self-hosted dashboard. Do not mix keys from Hexclave Cloud with a self-hosted API URL. ## Service Configuration ### Email Production auth flows need a real email provider. Configure Custom SMTP or Resend in the dashboard after first sign-in, or provide SMTP environment variables if you want default server-level email settings: ```env theme={null} STACK_EMAIL_HOST=smtp.example.com STACK_EMAIL_PORT=587 STACK_EMAIL_USERNAME=... STACK_EMAIL_PASSWORD=... STACK_EMAIL_SENDER=noreply@example.com STACK_EMAILABLE_API_KEY=disable_email_validation ``` Set `STACK_EMAILABLE_API_KEY` to an Emailable key if you want email validation. Use `disable_email_validation` only when you intentionally want to skip validation. The dashboard's Managed Domain email flow is an operator-managed integration. It requires additional server-side provider credentials such as `STACK_RESEND_API_KEY`, `STACK_DNSIMPLE_API_TOKEN`, and `STACK_DNSIMPLE_ACCOUNT_ID`. If you do not operate that integration, use Custom SMTP or your own Resend API key instead. ### Webhooks If you use webhooks, configure Svix: ```env theme={null} STACK_SVIX_API_KEY=... STACK_SVIX_SERVER_URL= ``` Leave `STACK_SVIX_SERVER_URL` empty when using Svix Cloud. Set it when you self-host Svix. If the browser and container need different Svix URLs, also set `NEXT_PUBLIC_STACK_SVIX_SERVER_URL` to the external URL. ### S3-Compatible Storage Configure S3-compatible storage for features that store assets: ```env theme={null} STACK_S3_ENDPOINT=https://s3.amazonaws.com STACK_S3_PUBLIC_ENDPOINT=https://your-public-bucket-url.example.com STACK_S3_REGION=us-east-1 STACK_S3_ACCESS_KEY_ID=... STACK_S3_SECRET_ACCESS_KEY=... STACK_S3_BUCKET=stack-storage STACK_S3_PRIVATE_BUCKET=stack-storage-private ``` ### AI and Custom Code Features Some dashboard AI features require OpenRouter: ```env theme={null} STACK_OPENROUTER_API_KEY=... ``` Custom, manual, and programmatic email sending requires Freestyle: ```env theme={null} STACK_FREESTYLE_API_KEY=... ``` ## Operations ### Upgrades Before upgrading: 1. Back up Postgres and any configured object storage. 2. Back up ClickHouse if you depend on analytics or external database sync data. 3. Pull the new Docker image. 4. Run the new image once with migrations enabled. 5. Verify dashboard sign-in, project loading, cron jobs, email sending, and any webhook flows you use. 6. Roll forward your application containers to the same image. The server image runs migrations by default. If you run multiple replicas, use your deployment system to ensure migrations run once before scaling the new version. ### Health Checks After deploy, verify: ```bash title="Terminal" theme={null} curl https://auth-api.example.com/api/v1/internal/backend-urls ``` Also test a complete sign-up or sign-in flow from your application, because redirect domains, email delivery, cron jobs, and SDK environment variables are the most common deployment issues. ### Common Issues #### The Dashboard Cannot Reach the API Check that `NEXT_PUBLIC_STACK_API_URL` is the public API URL and has no typo or unreachable internal hostname. #### Redirects Fail Add your application origin to the project's allowed domains in the dashboard. OAuth providers also need callback URLs that point at your self-hosted API URL. #### Emails Do Not Arrive Check the email provider configuration, sender domain verification, and any provider logs. For development-only email testing, use the [Local Emulator](/guides/going-further/local-emulator) instead of a production self-host deployment. #### You Cannot Access the Dashboard If you did not seed an admin user, temporarily enable internal project sign-up and rerun the seed script by restarting a container with seeding enabled: ```env theme={null} STACK_SEED_INTERNAL_PROJECT_SIGN_UP_ENABLED=true STACK_RUN_SEED_SCRIPT=true ``` After creating your admin account, disable sign-up again, restart with `STACK_SEED_INTERNAL_PROJECT_SIGN_UP_ENABLED=false`, and prefer a seeded admin user for future deployments. # Build a SaaS with Hexclave Source: https://docs.hexclave.com/guides/other/tutorials/build-a-saas-with-hexclave Bootstrap auth, resolve the signed-in user, protect your app, and ship-with optional pointers to teams, RBAC, and billing. This tutorial is a **generic SaaS path** on Hexclave: install and configure the SDK, wire sign-in, read the current user on the client and server, and gate your product. It does **not** assume teams, workspaces, or a particular permission model-those live in [Build a team-based app](/guides/other/tutorials/build-a-team-based-app) and the linked guides below. ## What you will have at the end * A working sign-in and account flow using Hexclave's handler routes (Next.js) or your framework’s equivalent. * A clear pattern for **who** is signed in across server components, client components, server actions, and route handlers. * **Protected** areas of your app (for example middleware on `/app/*` or `getUser({ or: "redirect" })` on key routes). * A short map for **where** multi-tenant and authorization work fits when you need it, plus a mindset for **production** domains, OAuth, and email. ## Prerequisites * A **Next.js** project using the **App Router** (Hexclave’s first-class path for hosted UI and handlers), or another stack supported in [Setup](/guides/getting-started/setup) (React, Express, or REST from any backend). * A Hexclave account and a **project** in the [dashboard](https://app.hexclave.com/projects). Hexclave does not officially support the Next.js Pages Router. If you are on Pages Router, consider the React or JavaScript SDKs per the [FAQ](/guides/faq). The examples below focus on **Next.js (App Router)**. The same ideas apply on other stacks-swap in `HexclaveClientApp` / REST calls as in [Setup](/guides/getting-started/setup). ## 1. Install Hexclave and wire environment variables The fastest path for JavaScript and TypeScript is the [Hexclave setup prompt](/guides/getting-started/setup). Choose **Next.js** in the setup builder, then copy the generated prompt into your coding agent. Then create or open a project in the dashboard and copy **project ID**, **publishable client key** (if your project uses one), and **secret server key** into your app configuration. For Next.js, that usually means `.env.local`: ```bash title=".env.local" theme={null} NEXT_PUBLIC_HEXCLAVE_PROJECT_ID= NEXT_PUBLIC_HEXCLAVE_PUBLISHABLE_CLIENT_KEY= HEXCLAVE_SECRET_SERVER_KEY= ``` ### What the setup prompt creates (Next.js) After setup, you should see files similar to: * `app/handler/[...hexclave]/page.tsx` - hosted sign-in, sign-up, account settings, and more * `app/layout.tsx` - wraps the app with `HexclaveProvider` and `HexclaveTheme` * `app/loading.tsx` - Suspense boundary for Hexclave async hooks * `hexclave/server.ts` - `hexclaveServerApp` for server components, actions, and route handlers * `hexclave/client.ts` - `hexclaveClientApp` when you need the client app object explicitly If you ever need to align manually with the wizard output, the core pieces look like this: ```typescript title="hexclave/server.ts" theme={null} import "server-only"; import { HexclaveServerApp } from "@hexclave/next"; export const hexclaveServerApp = new HexclaveServerApp({ tokenStore: "nextjs-cookie", }); ``` ```typescript title="hexclave/client.ts" theme={null} import { HexclaveClientApp } from "@hexclave/next"; export const hexclaveClientApp = new HexclaveClientApp({ // Reads NEXT_PUBLIC_HEXCLAVE_* from the environment by default }); ``` ```tsx title="app/handler/[...hexclave]/page.tsx" theme={null} import { HexclaveHandler } from "@hexclave/next"; import { hexclaveServerApp } from "@/hexclave/server"; export default function Handler(props: unknown) { return ; } ``` ```tsx title="app/layout.tsx" theme={null} import { HexclaveProvider, HexclaveTheme } from "@hexclave/next"; import { hexclaveServerApp } from "@/hexclave/server"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ```tsx title="app/loading.tsx" theme={null} export default function Loading() { return <>Loading...; } ``` Full variants (React, Express, Python, manual install) are in [Setup](/guides/getting-started/setup). After setup, open the hosted auth UI (for example `/handler/sign-up`), create a test user, and confirm you land back in your app. ### Marketing header: sign in / sign out Use `useHexclaveApp()` so navigation goes through Hexclave's redirect helpers: ```tsx title="components/auth-header.tsx" theme={null} "use client"; import { useHexclaveApp, useUser } from "@hexclave/next"; export function AuthHeader() { const app = useHexclaveApp(); const user = useUser(); return (
    {user ? ( <> {user.displayName ?? user.primaryEmail ?? user.id} ) : ( <> )}
    ); } ``` ## 2. Resolve the signed-in user everywhere Almost every SaaS screen starts from the **current user**: profile, preferences, billing state in your database, or admin vs end-user behavior you define yourself. ```tsx title="app/dashboard/page.tsx" theme={null} import { hexclaveServerApp } from "@/hexclave/server"; export default async function DashboardPage() { const user = await hexclaveServerApp.getUser(); if (!user) { return

    You are not signed in.

    ; } return

    Hello, {user.displayName ?? user.primaryEmail ?? user.id}

    ; } ```
    ```tsx title="app/app/page.tsx" theme={null} import { hexclaveServerApp } from "@/hexclave/server"; export default async function AppHomePage() { const user = await hexclaveServerApp.getUser({ or: "redirect" }); return

    Hello, {user.displayName ?? user.primaryEmail ?? user.id}

    ; } ```
    ```tsx title="components/greeting.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export function Greeting() { const user = useUser(); if (!user) { return

    Please sign in.

    ; } return

    Hello, {user.displayName ?? user.primaryEmail ?? user.id}

    ; } ```
    ```tsx title="components/greeting.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export function Greeting() { const user = useUser({ or: "redirect" }); return

    Hello, {user.displayName ?? user.primaryEmail ?? user.id}

    ; } ```
    ### Server action that requires a user `{ or: "throw" }` is useful when a redirect would be wrong (for example, from a form POST). The example below only needs Hexclave for identity; your own persistence layer stores product data keyed by `user.id`. ```tsx title="app/actions/onboarding.ts" theme={null} "use server"; import { hexclaveServerApp } from "@/hexclave/server"; export async function completeOnboardingStepAction(formData: FormData) { const user = await hexclaveServerApp.getUser({ or: "throw" }); const step = String(formData.get("step") ?? "").trim(); if (!step) { throw new Error("Step is required"); } // TODO: write to your database using user.id as the tenant key (or your own model). return { userId: user.id, step }; } ``` ### Route Handler (App Router API) ```tsx title="app/api/me/route.ts" theme={null} import { hexclaveServerApp } from "@/hexclave/server"; import { NextResponse } from "next/server"; export async function GET() { const user = await hexclaveServerApp.getUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } return NextResponse.json({ id: user.id, displayName: user.displayName, primaryEmail: user.primaryEmail, }); } ``` ### Middleware for a `/app` (or `/private`) section Match only the routes that should be gated, and **exclude** `/handler` so Hexclave auth pages keep working: ```tsx title="middleware.ts" theme={null} import { NextRequest, NextResponse } from "next/server"; import { hexclaveServerApp } from "@/hexclave/server"; export async function middleware(request: NextRequest) { const user = await hexclaveServerApp.getUser(); if (!user) { return NextResponse.redirect(new URL("/handler/sign-in", request.url)); } return NextResponse.next(); } export const config = { matcher: "/app/:path*", }; ``` More detail on protection patterns and sensitive HTML is in [User fundamentals](/guides/getting-started/user-fundamentals). Treat **client-side** checks as **UX only**. Anything that mutates data or exposes another customer’s data must be enforced again on the server (server components, server actions, route handlers, or your backend using the secret key or verified tokens). ## 3. Tenancy, teams, and permissions (when you need them) Stack gives you **users** and authentication primitives; **your** SaaS decides how rows and features map to customers. * **Single-user or simple B2C** - Often enough to key application data to `user.id` and enforce access in your API with the same user you resolved via `hexclaveServerApp.getUser()`. * **Shared accounts, workspaces, or B2B orgs** - Use Stack **teams** as the customer boundary, **team selection** for the active workspace, and **RBAC** for roles and fine-grained actions. Walk through that shape in [Build a team-based app](/guides/other/tutorials/build-a-team-based-app), with reference material in [Teams](/guides/apps/teams/overview), [Team selection](/guides/apps/teams/team-selection), and [RBAC](/guides/apps/rbac/overview). Non-JavaScript or custom frontends can use the [REST API](/api/overview) with the same project keys; the identity model you choose (user-only vs teams) stays consistent across clients. ## 4. Product polish: onboarding, email, and optional billing Hook flows to the guides-no extra Stack APIs are required at this layer: * **Onboarding and sign-up rules** - [User onboarding](/guides/apps/authentication/user-onboarding), [Sign-up rules](/guides/apps/authentication/sign-up-rules) * **Email** - [Emails](/guides/apps/emails/overview) * **Stripe / plans** - [Payments](/guides/apps/payments/overview) Example: after sign-up, send users to an onboarding route from your own `app/page.tsx` or a server layout once `getUser()` is non-null. ## 5. Production checklist Before going live, tighten **callback domains**, replace shared **OAuth** keys with your own provider apps where needed, and review email and security defaults. Follow [Launch checklist](/guides/apps/launch-checklist/overview). ## Related guides | Topic | Guide | | ----------------------------------------- | ------------------------------------------------------------------------ | | Install and configure | [Setup](/guides/getting-started/setup) | | `HexclaveApp` object | [HexclaveApp SDK reference](/sdk/objects/hexclave-app) | | Current user and page protection | [User fundamentals](/guides/getting-started/user-fundamentals) | | Teams, membership, and RBAC (deeper path) | [Build a team-based app](/guides/other/tutorials/build-a-team-based-app) | | Teams reference | [Teams](/guides/apps/teams/overview) | | Permissions | [RBAC](/guides/apps/rbac/overview) | | Pre-launch hardening | [Launch checklist](/guides/apps/launch-checklist/overview) | | Billing (optional) | [Payments](/guides/apps/payments/overview) | | General questions | [FAQ](/guides/faq) | ## FAQ No. This guide stays user-centric until you opt into teams. When multiple people share one customer account, follow [Build a team-based app](/guides/other/tutorials/build-a-team-based-app) and the [Teams](/guides/apps/teams/overview) docs. Use dashboard-defined permissions for **authorization** when you use RBAC; always enforce **business rules** on the server: Server Components, server actions, route handlers, or your backend with the **secret server key** or validated access tokens. Client checks alone are not enough for sensitive operations. Yes. Non-JS or custom frontends can use the [REST API](/api/overview) with the same project keys; the mental model (users, and optionally teams and permissions) stays the same. Localhost callback behavior and production domain restrictions are covered under **Domains** in [Launch checklist](/guides/apps/launch-checklist/overview). Keep localhost allowances enabled only for development. [Build a team-based app](/guides/other/tutorials/build-a-team-based-app) is the place for **teams**, **team selection**, and **RBAC** walkthroughs. This SaaS tutorial covers the **generic** product path: auth bootstrap, resolving the user, protecting routes, then linking out for tenancy and launch details. See [FAQ](/guides/faq) for contribution and community pointers. # Build a Team-Based App Source: https://docs.hexclave.com/guides/other/tutorials/build-a-team-based-app Model B2B tenants as teams, wire team selection and deep links, enforce RBAC on the server, and grow membership with invitations. This tutorial walks through a **multi-tenant** product where a **team** is the customer boundary (workspace, organization, account). You get membership-scoped team access, permission checks, team selection UX, and invitations. If you have not installed Stack yet (handler routes, `HexclaveProvider`, environment variables), start with [Build a SaaS with Hexclave](/guides/other/tutorials/build-a-saas-with-hexclave), then continue here. ## What you will have at the end * Teams listed and resolved **for the signed-in user** (safe deep links like `/team/[teamId]`). * **Team creation** from the client (with dashboard settings) and an optional **server** provisioning pattern. * A **team switcher** pattern using `SelectedTeamSwitcher` with deep links and optional `selectedTeam` updates. * **RBAC** checks aligned with dashboard-defined team permissions, with server-side enforcement before mutations. * A path to **invite** collaborators and accept invitations. ## Prerequisites * Hexclave installed as in [Build a SaaS with Hexclave](/guides/other/tutorials/build-a-saas-with-hexclave) (Next.js App Router examples below assume `hexclaveServerApp` in `stack/server.ts` and `@hexclave/next` in the app). * A project in the [dashboard](https://app.hexclave.com/projects) where you can edit **Teams** and **Team permissions**. On the server, prefer **`user.getTeam(id)`** and **`user.listTeams()`** from `hexclaveServerApp.getUser()` so you only ever load teams the current user belongs to. `hexclaveServerApp.getTeam` / `hexclaveServerApp.listTeams` operate at **project** scope (useful for admin or provisioning, not for normal tenant pages). ## 1. Turn on teams in the dashboard In your Stack project: 1. **Teams** - Enable team features and review defaults (for example whether a personal team is created on sign-up), per [Teams](/guides/apps/teams/overview). 2. **Client-side team creation** - If the browser will call `user.createTeam`, enable client team creation in team settings (same guide). 3. **Team permissions** - In **Team permissions**, define the actions your product needs (for example `export_reports`, nested under a role like `admin`). Add Stack **system** permissions where needed; their IDs start with `$` (for example `$invite_members`, `$read_members`, `$update_team`). See [RBAC](/guides/apps/rbac/overview). Keep client-side permission checks as **UX only**; always re-check on the server before changing data. ## 2. List teams and resolve a team from a URL Use the **current user** as the scope: `listTeams` / `useTeams`, and `getTeam` / `useTeam` for one id. ```tsx title="app/team/[teamId]/page.tsx" theme={null} import Link from "next/link"; import { hexclaveServerApp } from "@/stack/server"; type PageProps = { params: { teamId: string } }; export default async function TeamHomePage({ params }: PageProps) { const user = await hexclaveServerApp.getUser({ or: "redirect" }); const team = await user.getTeam(params.teamId); if (!team) { return

    You are not a member of this team.

    ; } return (

    {team.displayName}

    Team ID: {team.id}

    All teams
    ); } ```
    ```tsx title="app/team/[teamId]/team-home.tsx" theme={null} "use client"; import Link from "next/link"; import { useUser } from "@hexclave/next"; type Props = { teamId: string }; export function TeamHome({ teamId }: Props) { const user = useUser({ or: "redirect" }); const team = user.useTeam(teamId); if (!team) { return

    You are not a member of this team.

    ; } return (

    {team.displayName}

    Team ID: {team.id}

    All teams
    ); } ```
    If your framework types route `params` as a `Promise` (newer Next.js), `await` the params object before reading `teamId`. ### Index page: all workspaces ```tsx title="app/team/page.tsx" theme={null} import Link from "next/link"; import { hexclaveServerApp } from "@/stack/server"; export default async function TeamsIndexPage() { const user = await hexclaveServerApp.getUser({ or: "redirect" }); const teams = await user.listTeams(); return (
      {teams.map((team) => (
    • {team.displayName}
    • ))}
    ); } ```
    ```tsx title="app/team/page.tsx" theme={null} "use client"; import Link from "next/link"; import { useUser } from "@hexclave/next"; export default function TeamsIndexPage() { const user = useUser({ or: "redirect" }); const teams = user.useTeams(); return (
      {teams.map((team) => (
    • {team.displayName}
    • ))}
    ); } ```
    ## 3. Create teams ### Signed-in user creates a team (client) After enabling **client-side team creation**, the creator is added as a member with your project’s default creator permissions: ```tsx title="components/create-workspace-button.tsx" theme={null} "use client"; import { useRouter } from "next/navigation"; import { useUser } from "@hexclave/next"; export function CreateWorkspaceButton() { const user = useUser({ or: "redirect" }); const router = useRouter(); return ( ); } ``` ### Provision a team without a browser session (server) For imports, support tools, or other **server** jobs, `hexclaveServerApp.createTeam` creates a team at project scope (see [Teams](/guides/apps/teams/overview)); wire membership separately if your flow requires it. ```tsx title="scripts/provision-team.example.ts" theme={null} import { hexclaveServerApp } from "@/stack/server"; export async function provisionEmptyTeam(displayName: string) { return await hexclaveServerApp.createTeam({ displayName }); } ``` ## 4. Team selection and deep links Stack tracks a **`selectedTeam`** on the user for “current workspace” UX. For B2B apps, **deep links** (`/team/[teamId]`) are usually recommended so shared URLs always refer to the same tenant; see [Team selection](/guides/apps/teams/team-selection). `SelectedTeamSwitcher` updates `selectedTeam` when `selectedTeam` is passed, optionally navigates via `urlMap`, and can skip updating stored selection with `noUpdateSelectedTeam`: ```tsx title="components/team-switcher.tsx" theme={null} "use client"; import { SelectedTeamSwitcher, useUser } from "@hexclave/next"; type Props = { currentTeamId: string }; export function TeamSwitcher({ currentTeamId }: Props) { const user = useUser({ or: "redirect" }); const team = user.useTeam(currentTeamId); if (!team) { return null; } return ( `/team/${t.id}`} selectedTeam={team} /> ); } ``` Use `noUpdateSelectedTeam` when you only want navigation (for example “open workspace” without changing the persisted default). Details and examples are in [Team selection](/guides/apps/teams/team-selection). ## 5. Enforce RBAC (server + client) Define permissions in the dashboard, then branch on **`getPermission`** / **`usePermission`** on the **user**, scoped to a **team**. Replace `export_reports` with a permission you created. ```tsx title="app/team/[teamId]/reports/page.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; type PageProps = { params: { teamId: string } }; export default async function ReportsPage({ params }: PageProps) { const user = await hexclaveServerApp.getUser({ or: "redirect" }); const team = await user.getTeam(params.teamId); if (!team) { return

    Workspace not found.

    ; } const canExport = await user.getPermission(team, "export_reports"); if (!canExport) { return

    You do not have access to exports in this workspace.

    ; } return ; } ```
    ```tsx title="components/export-reports-button.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; import type { CurrentUser, Team } from "@hexclave/next"; type Props = { teamId: string }; /** Split so `usePermission` only runs when `useTeam` returned a real team (Rules of Hooks). */ export function ExportReportsButton({ teamId }: Props) { const user = useUser({ or: "redirect" }); const team = user.useTeam(teamId); if (!team) { return

    Workspace not found.

    ; } return ; } function ExportReportsButtonInner({ user, team }: { user: CurrentUser; team: Team }) { const permission = user.usePermission(team, "export_reports"); if (!permission) { return null; } return ; } ```
    System permissions use the same API, for example: ```tsx theme={null} const canInvite = await user.getPermission(team, "$invite_members"); ``` For listing effective permissions, use `listPermissions` / `usePermissions` ([RBAC](/guides/apps/rbac/overview)). ### Server action before a mutation ```tsx title="app/actions/reports.ts" theme={null} "use server"; import { hexclaveServerApp } from "@/stack/server"; export async function requestReportExportAction(teamId: string) { const user = await hexclaveServerApp.getUser({ or: "throw" }); const team = await user.getTeam(teamId); if (!team) { throw new Error("Not a member of this team"); } const canExport = await user.getPermission(team, "export_reports"); if (!canExport) { throw new Error("Forbidden"); } // TODO: enqueue export job scoped to team.id return { ok: true as const }; } ``` ## 6. Invitations and membership Users with **`$invite_members`** can invite by email from the `Team` object (options object in the SDK): ```tsx theme={null} await team.inviteUser({ email: "colleague@company.com" }); ``` Recipients with a **verified** email matching the invitation can list pending invites and accept: ```tsx title="components/pending-invites.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export function PendingInvites() { const user = useUser({ or: "redirect" }); const invitations = user.useTeamInvitations(); return (
      {invitations.map((inv) => (
    • {inv.teamDisplayName} - {inv.recipientEmail}
    • ))}
    ); } ``` Sender-side listing and revokes use `team.listInvitations` / `team.useInvitations` ([Teams](/guides/apps/teams/overview)). ## 7. Metadata, profiles, and members Teams support `clientMetadata`, `serverMetadata`, and `clientReadOnlyMetadata` on `team.update` ([Teams](/guides/apps/teams/overview)). Per-team member display uses **`getTeamProfile` / `useTeamProfile`** on the user; listing members uses **`team.listUsers` / `team.useUsers`** and requires **`$read_members`** on the client. ## Related guides | Topic | Guide | | ----------------------------------------- | -------------------------------------------------------------------------------- | | Auth bootstrap and route protection | [Build a SaaS with Hexclave](/guides/other/tutorials/build-a-saas-with-hexclave) | | Teams API reference | [Teams](/guides/apps/teams/overview) | | `SelectedTeamSwitcher` and URL strategies | [Team selection](/guides/apps/teams/team-selection) | | Permission modeling and nesting | [RBAC](/guides/apps/rbac/overview) | | REST without the SDK | [API overview](/api/overview) | | Pre-launch | [Launch checklist](/guides/apps/launch-checklist/overview) | ## FAQ For product pages and APIs acting as the signed-in user, use **`(await hexclaveServerApp.getUser()).getTeam(id)`** so membership is enforced by Stack. Use **`hexclaveServerApp.getTeam`** only when you intentionally need **project-wide** team lookup (for example internal admin tools), then apply your own authorization. React hooks must run unconditionally. `useTeam` may leave you without a `Team` instance; `usePermission` needs that team. A small wrapper that returns early, and an inner component that only calls `usePermission` when `team` is defined, keeps hooks valid. Stack permissions answer **whether this Stack user may perform an action in this Stack team**. You should still scope **your** rows by `team.id` (or equivalent) and validate inputs-Stack does not replace your data model. # Ship Production-Ready Auth Source: https://docs.hexclave.com/guides/other/tutorials/ship-production-ready-auth Lock down page and API access, handle secrets and environments, then align domains, OAuth, email, webhooks, and dashboard production mode with Hexclave. Going live is not only “turning on production mode.” This guide focuses on **what must be true** so only signed-in users reach protected surfaces, **secrets stay server-only**, and Stack’s dev-friendly defaults are replaced with **your** domains, OAuth apps, and email. If you are still wiring Stack into your app, complete [Build a SaaS with Hexclave](/guides/other/tutorials/build-a-saas-with-hexclave) first. For team RBAC before launch, see [Build a team-based app](/guides/other/tutorials/build-a-team-based-app). ## What you will have at the end * A clear model for **protecting pages**, layouts, route handlers, and server actions—and when to use middleware vs in-render checks. * Awareness of **Next.js + sensitive HTML** so you do not leak data through composition. * A **secrets and environment** split that keeps the server key out of the browser. * A **launch-aligned** checklist (domains, OAuth, email, production mode) with pointers to deeper docs. * A **webhook** verification mindset (signed payloads only). ## 1. Protect a page (and know what that actually guarantees) You typically combine **one or more** of: 1. **Middleware** — cheap gate when the URL alone tells you the area is private (for example everything under `/app`). 2. **Server Components / server loaders** — `await hexclaveServerApp.getUser({ or: "redirect" })` (or handle `null`) on the route that renders sensitive UI. 3. **Client Components** — `useUser({ or: "redirect" })` for UX; **never** the only layer for authorization. ```tsx title="middleware.ts" theme={null} import { NextRequest, NextResponse } from "next/server"; import { hexclaveServerApp } from "@/stack/server"; export async function middleware(request: NextRequest) { const user = await hexclaveServerApp.getUser(); if (!user) { return NextResponse.redirect(new URL("/handler/sign-in", request.url)); } return NextResponse.next(); } export const config = { matcher: "/app/:path*", }; ``` Match **only** prefixes that should be gated. Do **not** blanket-match `/` or you can block static assets and Stack’s **`/handler`** routes (sign-in, sign-up, callbacks). If your project uses hosted components or cross-domain auth, prefer protecting pages with `hexclaveServerApp.getUser({ or: "redirect" })` in a Server Component. Middleware cannot run the runtime `redirectToSignIn()` helper, so only hard-code `/handler/sign-in` here when your app owns a same-domain handler route. ```tsx title="app/app/dashboard/page.tsx" theme={null} import { hexclaveServerApp } from "@/stack/server"; export default async function DashboardPage() { await hexclaveServerApp.getUser({ or: "redirect" }); return

    Dashboard

    ; } ```
    ```tsx title="app/app/settings/client-gate.tsx" theme={null} "use client"; import { useUser } from "@hexclave/next"; export function SettingsGate({ children }: { children: React.ReactNode }) { useUser({ or: "redirect" }); return <>{children}; } ```
    ### `redirect` vs `throw` * **`{ or: "redirect" }`** — use when a browser navigation is appropriate (pages, most Server Components). * **`{ or: "throw" }`** — use in **server actions**, **route handlers**, and other places where redirecting would be wrong; map errors to `401`/`403` responses yourself. ```tsx title="app/api/me/route.ts" theme={null} import { hexclaveServerApp } from "@/stack/server"; import { NextResponse } from "next/server"; export async function GET() { try { const user = await hexclaveServerApp.getUser({ or: "throw" }); return NextResponse.json({ id: user.id }); } catch { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } ``` ### Sensitive content and Next.js composition Authentication prevents **impersonation**, but **HTML composition** still matters: * **Client Components** ship to the browser; gating with hooks does not hide their bundled code. * **Server Components**: a protected parent does **not** guarantee an unprotected child (or an unprotected segment under a layout) never reaches the client. Anything that embeds **secrets or PII** should **itself** call `getUser({ or: "redirect" })` / `getUser({ or: "throw" })` (or otherwise avoid rendering that data when unauthenticated). * **Middleware**: on **Next.js 15.2.3+**, middleware-only protection does not leak unprotected server-rendered fragments the way older versions could; still treat **authorization** (who may perform an action) as a **server** concern. Read the full discussion in [User fundamentals — Protecting a page](/guides/getting-started/user-fundamentals#protecting-a-page). Treat **client-side** checks as **UX only**. Anything that mutates data or exposes another customer’s data must be enforced on the server (Server Components, server actions, route handlers, or your backend with the **secret server key** or validated tokens). For team-scoped apps, re-check [RBAC](/guides/apps/rbac/overview) on the server, not only with `usePermission`. ## 2. Secrets, keys, and environments **`HEXCLAVE_SECRET_SERVER_KEY`** (or `ssk_...`) must **only** exist in server-side environments (SSR, route handlers, server actions, your backend). Never prefix it with `NEXT_PUBLIC_`, never import it from code that runs in the browser, and never log it. See the [HexclaveApp SDK reference](/sdk/objects/hexclave-app) and the [REST API overview](/api/overview). Practical split: | Variable | Typical exposure | Use | | ------------------------------------------------------- | ---------------- | --------------------------------------------------------- | | `NEXT_PUBLIC_HEXCLAVE_PROJECT_ID` | Browser + server | Identifies the project to the hosted UI and client SDK. | | `NEXT_PUBLIC_HEXCLAVE_PUBLISHABLE_CLIENT_KEY` (if used) | Browser + server | Client-safe key where your project uses publishable keys. | | `HEXCLAVE_SECRET_SERVER_KEY` | **Server only** | Elevated server SDK and REST **server** API. | Use **separate** Stack projects or at least **separate** env values for production vs staging when possible. Rotate keys from the dashboard if a secret is exposed. ## 3. Domains, OAuth, email, and production mode Stack’s dev defaults (localhost callbacks, shared OAuth keys, shared mail) are convenient but **not** what you want for real users. Add your real **`https://…` origin** under **Domain & Handlers** and disable **Allow all localhost callbacks** when you no longer need local redirects against production configuration. Details: [Launch checklist — Domains](/guides/apps/launch-checklist/overview#domains). Create **your own** OAuth clients per provider, set the provider callback URLs Stack documents, then paste **your** client ID and secret in the dashboard (leave shared keys for local dev only). Details: [Launch checklist — OAuth providers](/guides/apps/launch-checklist/overview#oauth-providers) and [Auth providers](/guides/apps/authentication/auth-providers). Point outbound mail at **your** SMTP/domain so magic links and invitations come from a domain users trust. Details: [Launch checklist — Email server](/guides/apps/launch-checklist/overview#email-server) and [Emails](/guides/apps/emails/overview). After the above, turn on **production mode** in **Project Settings** so dashboard guardrails match how you run in prod ([Launch checklist — Enabling production mode](/guides/apps/launch-checklist/overview#enabling-production-mode)). ## 4. Webhooks If you consume Stack webhooks, **verify every payload** (for example with Svix and `STACK_WEBHOOK_SECRET`) before acting on events—treat unsigned or failed verification as `400`. Implementation patterns: [Webhooks](/guides/apps/webhooks/overview). ## 5. Before you flip traffic * **Smoke-test** sign-in, sign-up, password reset, and OAuth **on the production domain** after DNS and env vars are final. * **Confirm** redirect URLs in third-party consoles (OAuth, IdP) match the Stack callback URLs you use in prod. * **Align** session / cookie behavior with your hosting (same-site, HTTPS, reverse proxies) per your platform docs. * **Plan rollback**: keep prior env values or a maintenance window note so you can revert dashboard or env changes quickly. ## Related guides | Topic | Guide | | -------------------------------- | -------------------------------------------------------------------------------- | | First integration | [Build a SaaS with Hexclave](/guides/other/tutorials/build-a-saas-with-hexclave) | | Page protection details | [User fundamentals](/guides/getting-started/user-fundamentals) | | Domains, OAuth, email, prod mode | [Launch checklist](/guides/apps/launch-checklist/overview) | | `HexclaveServerApp` and keys | [HexclaveApp SDK reference](/sdk/objects/hexclave-app) | | Webhook verification | [Webhooks](/guides/apps/webhooks/overview) | | REST from your backend | [API overview](/api/overview) | ## FAQ Middleware runs on **matching routes** and is great for coarse “must be logged in” gates. **Route handlers** and **server actions** should still call `getUser({ or: "throw" })` (or equivalent) and return proper HTTP errors—middleware will not run for every internal call path, and **authorization** (what that user may do) belongs next to your business logic. Prefer **separate projects** (or strictly separated keys and OAuth clients) so you never point production OAuth callbacks at localhost, and so a leaked dev key cannot touch production data. Model permissions in the dashboard, then enforce them on the server for every sensitive operation. Walkthrough: [Build a team-based app](/guides/other/tutorials/build-a-team-based-app). # Welcome Source: https://docs.hexclave.com/index Hexclave documentation for setup, components, SDK usage, and REST APIs.

    Agent-first setup

    Set up with one prompt.

    Copy the full Hexclave setup prompt into your coding agent, or follow the manual instructions.

    Navigate Through Our Docs

    Start at the top and work your way down, or jump straight to the section you need.

    Recommended Order

    Getting Started

    First-time setup, install the SDK, get auth running in minutes.

    Setup Users AI Integration Production
    Explore Apps
    Going Further

    Compare development flows, configure your project, and use lower-level interfaces where needed.

    Local vs. Cloud Config File CLI
    SDK Reference

    Use hooks, objects, and types to read and write auth data directly in your app code.

    useUser StackApp User Type
    REST API

    Integrate Hexclave from any backend or language through HTTP endpoints and webhook flows.

    Overview Webhooks Backend Setup

    Resources

    Reach for product access, source code, and support channels when you need more than reference docs.

    Manage projects, keys, providers, and authentication settings. Explore the open-source codebase, examples, and release history. Ask questions, share feedback, and get help from the community.
    # Migrating from Stack Auth to Hexclave Source: https://docs.hexclave.com/migration How to migrate your application from the legacy @stackframe/* packages to @hexclave/*. Stack Auth is now Hexclave. The dashboard, APIs, and your data are unchanged — only the brand and the package names changed. Both backends (`api.stack-auth.com` and `api.hexclave.com`) point at the same service, so each `@stackframe/*` package continues to work; staying on the legacy SDK requires no action. This guide is for projects that want to migrate to `@hexclave/*` packages. ## 1. Install the new packages Replace each `@stackframe/*` dependency with its `@hexclave/*` equivalent: ```bash theme={null} npm uninstall @stackframe/stack npm install @hexclave/next ``` | Old package | New package | | ------------------- | ----------------- | | `@stackframe/stack` | `@hexclave/next` | | `@stackframe/react` | `@hexclave/react` | | `@stackframe/js` | `@hexclave/js` | Rename your imports from `@stackframe/*` to `@hexclave/*`. The public API surface is identical, except that all `Stack*` references are renamed to `Hexclave*`: ```ts title="Before" theme={null} import { StackClientApp, StackProvider, useStackApp } from "@stackframe/stack"; ``` ```ts title="After" theme={null} import { HexclaveClientApp, HexclaveProvider, useHexclaveApp } from "@hexclave/next"; ``` ## 2. Update hardcoded references Sweep your codebase and replace: * `https://api.stack-auth.com` → `https://api.hexclave.com` ## Optional changes All legacy names keep working — rename only if you want your code to match the new brand. * **Request headers**: `X-Stack-*` → `X-Hexclave-*`. * **Environment variables**: `STACK_*` → `HEXCLAVE_*`. * **Bearer prefix**: `stackauth_*` tokens remain valid. * **CLI binary**: both `stack` and `hexclave` ship with `@hexclave/cli`. * **Hosted-handler subdomain**: `.built-with-stack-auth.com` still works. ## Other If your backend verifies Hexclave-issued JWTs directly (for example with `jose.jwtVerify`), update the expected `iss` claim — `@hexclave/*` SDKs sign tokens under the hexclave host: ```ts title="Before" theme={null} const { payload } = await jose.jwtVerify(token, jwks, { issuer: 'https://api.stack-auth.com/api/v1/projects/YOUR_PROJECT_ID', audience: 'YOUR_PROJECT_ID', }); ``` ```ts title="After" theme={null} const { payload } = await jose.jwtVerify(token, jwks, { issuer: 'https://api.hexclave.com/api/v1/projects/YOUR_PROJECT_ID', audience: 'YOUR_PROJECT_ID', }); ``` The same applies to the anonymous (`/api/v1/projects-anonymous-users/...`) and restricted (`/api/v1/projects-restricted-users/...`) issuer variants. You don't need to update your OAuth provider callback URLs — your current `api.stack-auth.com` callback URLs keep working. However, if you recreate an OAuth provider on the dashboard, you'll need to use the new callback URL: ``` https://api.hexclave.com/api/v1/auth/oauth/callback/ ``` Questions? [Discord](https://discord.hexclave.com) or [team@hexclave.com](mailto:team@hexclave.com). # useHexclaveApp Source: https://docs.hexclave.com/sdk/hooks/use-hexclave-app React hook to retrieve the HexclaveClientApp instance from the HexclaveProvider. The `useHexclaveApp` hook returns a `HexclaveClientApp` object from the one that you provided in the setup flow. If you want to learn more about the `HexclaveClientApp` object, check out the [StackApp](/sdk/objects/hexclave-app) documentation. ## Usage ```jsx theme={null} import { useHexclaveApp } from "@hexclave/next"; // replace `next` with the correct framework SDK package function MyComponent() { const hexclaveApp = useHexclaveApp(); return ; } ``` ## Related * [Setup guide](/guides/getting-started/setup) * [StackApp object reference](/sdk/objects/hexclave-app) # useUser Source: https://docs.hexclave.com/sdk/hooks/use-user React hook to access the current authenticated user. This standalone React hook is an alias for `useHexclaveApp().useUser()`. It only exists for convenience; it does not have any additional functionality. For more information, please refer to the [documentation for `hexclaveClientApp.useUser()`](/sdk/objects/hexclave-app#stackclientappuseuseroptions). ## Usage ```jsx theme={null} import { useUser } from "@hexclave/next"; // replace `next` with the correct framework SDK package function MyComponent() { const user = useUser(); if (!user) { return
    Not signed in
    ; } return
    Hello, {user.displayName}
    ; } ``` ## Related * [StackApp object reference](/sdk/objects/hexclave-app) * [User type reference](/sdk/types/user) # StackApp Source: https://docs.hexclave.com/sdk/objects/hexclave-app Reference documentation for HexclaveClientApp and HexclaveServerApp objects. This is a detailed reference for the `StackApp` object. For setup instructions, see [Setup](/guides/getting-started/setup). ## Overview * [HexclaveClientApp](#stackclientapp) - Client-level permissions for frontend code * [HexclaveServerApp](#stackserverapp) - Server-level permissions with full access *** # HexclaveClientApp A `StackApp` with client-level permissions. It contains most of the useful methods and hooks for your client-side code. Most commonly you get an instance of `HexclaveClientApp` by calling [`useHexclaveApp()`](/sdk/hooks/use-hexclave-app) in a Client Component. ## Table of Contents ```typescript theme={null} type HexclaveClientApp = { new(options): HexclaveClientApp; getUser([options]): Promise; useUser([options]): User; getProject(): Promise; useProject(): Project; signInWithOAuth(provider): void; signInWithCredential([options]): Promise<...>; signUpWithCredential([options]): Promise<...>; sendForgotPasswordEmail(email): Promise<...>; sendMagicLinkEmail(email): Promise<...>; }; ``` ## Constructor Creates a new `HexclaveClientApp` instance. Because each app creates a new connection to Hexclave's backend, you should re-use existing instances wherever possible. This object is not usually constructed directly. More commonly, you would construct a [`HexclaveServerApp`](#stackserverapp) instead, pass it into your app setup (see the [setup guide](/guides/getting-started/setup)), and then use the `useHexclaveApp()` hook to obtain a `HexclaveClientApp`. The [setup wizard](/guides/getting-started/setup) does these steps for you, so you don't need to worry about it unless you are manually setting up Hexclave. If you're building a client-only app and don't have a `HEXCLAVE_SECRET_SERVER_KEY`, you can construct a `HexclaveClientApp` directly. Token storage configuration. Base URL for the Hexclave API. Project ID. Defaults to the `NEXT_PUBLIC_HEXCLAVE_PROJECT_ID` environment variable. Publishable client key. Defaults to the `NEXT_PUBLIC_HEXCLAVE_PUBLISHABLE_CLIENT_KEY` environment variable. Redirect URL configuration. Analytics capture configuration. SDK-managed capture is enabled by default; pass `{ enabled: false }` to disable it entirely, or `{ replays: { enabled: true } }` to record session replays. Disable automatic prefetching. ```typescript theme={null} declare class HexclaveClientApp { constructor(options: { tokenStore: "nextjs-cookie" | "cookie" | { accessToken: string; refreshToken: string; } | Request; baseUrl?: string; projectId?: string; publishableClientKey?: string; urls?: object; analytics?: object; noAutomaticPrefetch?: boolean; }); } ``` ```typescript theme={null} const hexclaveClientApp = new HexclaveClientApp({ tokenStore: "nextjs-cookie", baseUrl: "https://api.hexclave.com", projectId: "123", publishableClientKey: "123", urls: { home: "/", }, }); ``` ```typescript theme={null} "use client"; function MyReactComponent() { const hexclaveClientApp = useHexclaveApp(); } ``` ## User Management ### `hexclaveClientApp.getUser([options])` Gets the current user. What to do if the user is not found. ```typescript theme={null} declare function getUser(options?: { or?: "return-null" | "redirect" | "throw"; }): Promise; ``` ```typescript theme={null} const userOrNull = await hexclaveClientApp.getUser(); console.log(userOrNull); const user = await hexclaveClientApp.getUser({ or: "redirect" }); console.log(user); ``` ### `hexclaveClientApp.useUser([options])` React hook version of `getUser()`. Equivalent to the [`useUser()`](/sdk/hooks/use-user) standalone hook, which is an alias for `useHexclaveApp().useUser()`. What to do if the user is not found. ```typescript theme={null} declare function useUser(options?: { or?: "return-null" | "redirect" | "throw"; }): CurrentUser | null; ``` ```jsx theme={null} "use client"; function MyReactComponent() { const user = useUser(); return user ?
    Hello, {user.name}
    :
    Not signed in
    ; } ``` ```tsx theme={null} "use client"; function MyProtectedComponent() { useUser({ or: "redirect" }); return
    You can only see this if you are authenticated
    ; } ```
    ### `hexclaveClientApp.getProject()` Gets the current project. ```typescript theme={null} declare function getProject(): Promise; ``` ```typescript theme={null} const project = await hexclaveClientApp.getProject(); ``` ### `hexclaveClientApp.useProject()` React hook version of `getProject()`. ```typescript theme={null} declare function useProject(): Project; ``` ```typescript theme={null} function MyReactComponent() { const project = useProject(); } ``` ## Authentication ### `hexclaveClientApp.signInWithOAuth(provider)` Initiates the OAuth sign-in process with the specified provider. The OAuth provider type. ```typescript theme={null} declare function signInWithOAuth(provider: string): Promise; ``` ```typescript theme={null} await hexclaveClientApp.signInWithOAuth("google"); ``` ### `hexclaveClientApp.signInWithCredential([options])` Sign in using email and password credentials. User's email. User's password. Whether to skip redirect after sign-in. ```typescript theme={null} declare function signInWithCredential(options?: { email?: string; password?: string; noRedirect?: boolean; }): Promise>; ``` ```typescript theme={null} const result = await hexclaveClientApp.signInWithCredential({ email: "test@example.com", password: "password", }); if (result.status === "error") { console.error("Sign in failed", result.error.message); } ``` ### `hexclaveClientApp.signUpWithCredential([options])` Sign up using email and password credentials. User's email. User's password. Whether to skip redirect after sign-up. ```typescript theme={null} declare function signUpWithCredential(options?: { email?: string; password?: string; noRedirect?: boolean; }): Promise>; ``` ```typescript theme={null} const result = await hexclaveClientApp.signUpWithCredential({ email: "test@example.com", password: "password", }); if (result.status === "error") { console.error("Sign up failed", result.error.message); } ``` ### `hexclaveClientApp.sendForgotPasswordEmail(email)` Send a forgot-password email to an email address. The email to send the forgot-password email to. ```typescript theme={null} declare function sendForgotPasswordEmail( email: string, ): Promise>; ``` ```typescript theme={null} const result = await hexclaveClientApp.sendForgotPasswordEmail("test@example.com"); if (result.status === "success") { console.log("Forgot password email sent"); } else { console.error("Failed to send forgot password email", result.error.message); } ``` ### `hexclaveClientApp.sendMagicLinkEmail(email)` Send a magic-link or OTP sign-in email to an email address. The email to send the magic link to. ```typescript theme={null} declare function sendMagicLinkEmail( email: string, ): Promise>; ``` ```typescript theme={null} const result = await hexclaveClientApp.sendMagicLinkEmail("test@example.com"); ``` *** # HexclaveServerApp Like `HexclaveClientApp`, but with server permissions. Has full read and write access to all users. Since this functionality should only be available in environments you trust (ie. your own server), it requires a `HEXCLAVE_SECRET_SERVER_KEY`. In some cases, you may want to use a `HexclaveServerApp` on the client; an example for this is an internal dashboard that only your own employees have access to. We generally recommend against doing this unless you are aware of and protected against the (potentially severe) security implications of exposing `HEXCLAVE_SECRET_SERVER_KEY` on the client. ## Table of Contents ```typescript theme={null} type HexclaveServerApp = // Inherits all functionality from HexclaveClientApp & HexclaveClientApp & { new(options): HexclaveServerApp; getUser([id][, options]): Promise; useUser([id][, options]): ServerUser; listUsers([options]): Promise; useUsers([options]): ServerUser[]; createUser([options]): Promise; sendEmail(options): Promise>; getTeam(id): Promise; useTeam(id): ServerTeam; listTeams([options]): Promise; useTeams([options]): ServerTeam[]; listTeamsPaginated([options]): Promise<{ items: ServerTeam[]; nextCursor: string | null }>; useTeamsPaginated([options]): { items: ServerTeam[]; nextCursor: string | null }; createTeam([options]): Promise; } ``` ## Constructor Creates a new `HexclaveServerApp` instance. Token storage configuration. Base URL for the Hexclave API. Project ID. Publishable client key. Secret server key. Defaults to the `HEXCLAVE_SECRET_SERVER_KEY` environment variable. Redirect URL configuration. Disable automatic prefetching. ```typescript theme={null} declare class HexclaveServerApp { constructor(options: { tokenStore: "nextjs-cookie" | "cookie" | { accessToken: string; refreshToken: string; } | Request; baseUrl?: string; projectId?: string; publishableClientKey?: string; secretServerKey?: string; urls?: object; noAutomaticPrefetch?: boolean; }); } ``` ```typescript theme={null} const hexclaveServerApp = new HexclaveServerApp({ tokenStore: "nextjs-cookie", urls: { signIn: "/my-custom-sign-in-page", }, }); ``` ## User Operations ### `hexclaveServerApp.getUser([id], [options])` Enhanced version of `HexclaveClientApp.getUser()` with server permissions. **Overloads:** 1. `getUser(id: string): Promise` to get a user by ID. 2. `getUser(options?: { or?: "return-null" | "redirect" | "throw" }): Promise` to get the current user. ```typescript theme={null} declare function getUser(id: string): Promise; declare function getUser(options?: { or?: "return-null" | "redirect" | "throw"; }): Promise; ``` ```typescript theme={null} const currentUser = await hexclaveServerApp.getUser(); console.log(currentUser); const serverUser = await hexclaveServerApp.getUser( "12345678-1234-1234-1234-123456789abc", ); console.log(serverUser); ``` ### `hexclaveServerApp.useUser([id], [options])` Functionally equivalent to [`getUser()`](#stackserverappgetuserid-options), but as a React hook. This should be used on the server-side only. ```typescript theme={null} declare function useUser( idOrOptions?: string | { or?: "return-null" | "redirect" | "throw" }, options?: { or?: "return-null" | "redirect" | "throw" }, ): ServerUser | CurrentServerUser | null; ``` ### `hexclaveServerApp.listUsers([options])` Lists all users on the project. The cursor to start the result set from. Maximum number of items to return. If omitted, all users are returned. The field to sort results by. Whether to sort in descending order. Defaults to `false`. Free-text search on the user's display name and emails. ```typescript theme={null} declare function listUsers(options?: { cursor?: string; limit?: number; orderBy?: "signedUpAt"; desc?: boolean; query?: string; }): Promise; ``` ```typescript theme={null} const users = await hexclaveServerApp.listUsers({ limit: 20 }); console.log(users); if (users.nextCursor) { const nextPageUsers = await hexclaveServerApp.listUsers({ cursor: users.nextCursor, limit: 20, }); console.log(nextPageUsers); } ``` ### `hexclaveServerApp.useUsers([options])` Functionally equivalent to [`listUsers()`](#stackserverapplistusersoptions), but as a React hook. This should be used on the server-side only. ```typescript theme={null} declare function useUsers(options?: { cursor?: string; limit?: number; orderBy?: "signedUpAt"; desc?: boolean; query?: string; }): ServerUser[]; ``` ### `hexclaveServerApp.createUser([options])` Creates a new user from the server. User's primary email. Whether the email is verified. Whether email auth is enabled. User's password. Enable OTP or magic-link auth. User's display name. ```typescript theme={null} declare function createUser(options?: { primaryEmail?: string; primaryEmailVerified?: boolean; primaryEmailAuthEnabled?: boolean; password?: string; otpAuthEnabled?: boolean; displayName?: string; }): Promise; ``` ```typescript theme={null} const passwordUser = await hexclaveServerApp.createUser({ primaryEmail: "test@example.com", primaryEmailAuthEnabled: true, password: "password123", }); const magicLinkUser = await hexclaveServerApp.createUser({ primaryEmail: "test@example.com", primaryEmailVerified: true, primaryEmailAuthEnabled: true, otpAuthEnabled: true, }); ``` ### `hexclaveServerApp.sendEmail(options)` Send custom emails to users. You can send either custom HTML emails or use predefined templates with variables. Email configuration and content. The method returns a `Result` that can include `RequiresCustomEmailServer`, `SchemaError`, and `UserIdDoesNotExist`. ```typescript theme={null} declare function sendEmail( options: SendEmailOptions, ): Promise>; ``` ```typescript theme={null} const htmlResult = await hexclaveServerApp.sendEmail({ userIds: ["user-1", "user-2"], subject: "Welcome to our platform!", html: "

    Welcome!

    Thanks for joining us.

    ", }); if (htmlResult.status === "error") { console.error("Failed to send email:", htmlResult.error); } ``` ```typescript theme={null} const templateResult = await hexclaveServerApp.sendEmail({ userIds: ["user-1"], templateId: "welcome-template", variables: { userName: "John Doe", activationUrl: "https://app.com/activate/token123", }, }); ```
    ## Team Management ### `hexclaveServerApp.getTeam(id)` Get a team by its ID. Team ID. ```typescript theme={null} declare function getTeam(id: string): Promise; ``` ```typescript theme={null} const team = await hexclaveServerApp.getTeam("team_id_123"); ``` ### `hexclaveServerApp.useTeam(id)` Functionally equivalent to [`getTeam(id)`](#stackserverappgetteamid), but as a React hook. This should be used on the server-side only. ```typescript theme={null} declare function useTeam(id: string): ServerTeam; ``` ### `hexclaveServerApp.listTeams([options])` Lists all teams on the current project. For cursor-based pagination over teams, see [`listTeamsPaginated`](#stackserverapplistteamspaginatedoptions). The field to sort results by. Whether to sort in descending order. Defaults to `false`. ```typescript theme={null} declare function listTeams(options?: { orderBy?: "createdAt"; desc?: boolean; }): Promise; ``` ```typescript theme={null} const teams = await hexclaveServerApp.listTeams(); console.log(teams); ``` ### `hexclaveServerApp.useTeams([options])` Functionally equivalent to [`listTeams()`](#stackserverapplistteamsoptions), but as a React hook. This should be used on the server-side only. ```typescript theme={null} declare function useTeams(options?: { orderBy?: "createdAt"; desc?: boolean; }): ServerTeam[]; ``` ### `hexclaveServerApp.listTeamsPaginated([options])` Lists teams on the current project with cursor-based pagination, optional filtering, and ordering. The returned array carries an extra `nextCursor` property; pass it back as `cursor` to load the next page. Cursor returned as `nextCursor` from a previous response. Maximum number of items to return. If omitted, all matching teams are returned. The field to sort results by. Whether to sort in descending order. Defaults to `false`. Free-text search on the team's display name (and team ID if the query is a UUID). ```typescript theme={null} declare function listTeamsPaginated(options?: { cursor?: string; limit?: number; orderBy?: "createdAt"; desc?: boolean; query?: string; }): Promise<{ items: ServerTeam[]; nextCursor: string | null }>; ``` ```typescript theme={null} const teams = await hexclaveServerApp.listTeamsPaginated({ limit: 20 }); console.log(teams); if (teams.nextCursor) { const nextPageTeams = await hexclaveServerApp.listTeamsPaginated({ cursor: teams.nextCursor, limit: 20, }); console.log(nextPageTeams); } ``` ### `hexclaveServerApp.useTeamsPaginated([options])` Functionally equivalent to [`listTeamsPaginated()`](#stackserverapplistteamspaginatedoptions), but as a React hook. This should be used on the server-side only. ```typescript theme={null} declare function useTeamsPaginated(options?: { cursor?: string; limit?: number; orderBy?: "createdAt"; desc?: boolean; query?: string; }): { items: ServerTeam[]; nextCursor: string | null }; ``` ### `hexclaveServerApp.createTeam([options])` Creates a team without adding a user to it. Team display name. Team profile image URL. ```typescript theme={null} declare function createTeam(options?: { displayName?: string; profileImageUrl?: string | null; }): Promise; ``` ```typescript theme={null} const team = await hexclaveServerApp.createTeam({ displayName: "New Team", profileImageUrl: "https://example.com/profile.jpg", }); ``` # SDK Overview Source: https://docs.hexclave.com/sdk/overview The SDK reference for Hexclave's Next.js SDK. This is the SDK reference for Hexclave's Next.js SDK. For setup instructions and how to use the SDK, see [Setup & Installation](/guides/getting-started/setup). If you are using a framework or programming language other than Next.js, you can use [our REST API](/api/overview). ## General | Type | Description | | ------------------------------------------------------------- | ------------------------------------------ | | [HexclaveClientApp](/sdk/objects/hexclave-app#stackclientapp) | Client-level permissions for frontend code | | [HexclaveServerApp](/sdk/objects/hexclave-app#stackserverapp) | Server-level permissions with full access | | [Project](/sdk/types/project#project) | Project configuration and settings | ## Users & User Data | Type | Description | | ----------------------------------------------------------------------- | ------------------------------------------------- | | [CurrentUser](/sdk/types/user#currentuser) | Client-side authenticated user | | [ServerUser](/sdk/types/user#serveruser) | Server-side user with full access | | [CurrentServerUser](/sdk/types/user#currentserveruser) | Current user with server permissions | | [ContactChannel](/sdk/types/contact-channel#contactchannel) | User contact information (email, phone) | | [ServerContactChannel](/sdk/types/contact-channel#servercontactchannel) | Server-side contact channel with extended control | ## Teams | Type | Description | | ----------------------------------------------------------------------- | --------------------------------- | | [Team](/sdk/types/team#team) | Team management and configuration | | [ServerTeam](/sdk/types/team#serverteam) | Server-side team with full access | | [TeamPermission](/sdk/types/team-permission#teampermission) | Team-level permissions | | [ServerTeamPermission](/sdk/types/team-permission#serverteampermission) | Server-side team permissions | | [TeamUser](/sdk/types/team-user#teamuser) | Team membership representation | | [ServerTeamUser](/sdk/types/team-user#serverteamuser) | Server-side team user | | [TeamProfile](/sdk/types/team-profile#teamprofile) | Team-specific user profiles | | [ServerTeamProfile](/sdk/types/team-profile#serverteamprofile) | Server-side team profiles | ## Email | Type | Description | | ----------------------------------------------------- | --------------------------- | | [SendEmailOptions](/sdk/types/email#sendemailoptions) | Email sending configuration | ## Payments & Items | Type | Description | | ---------------------------------------- | -------------------------------------------------------- | | [Customer](/sdk/types/customer#customer) | Customer billing interface | | [Item](/sdk/types/item#item) | Client-side resource management | | [ServerItem](/sdk/types/item#serveritem) | Server-side resource management with quantity operations | ## Hooks | Hook | Description | | --------------------------------------------- | --------------------------------------------------- | | [useHexclaveApp](/sdk/hooks/use-hexclave-app) | React hook to access the HexclaveClientApp instance | | [useUser](/sdk/hooks/use-user) | React hook to access the current user | ## API Keys | Type | Description | | ---------------------------- | ------------------------------------------ | | [ApiKey](/sdk/types/api-key) | API key management for programmatic access | # ApiKey Source: https://docs.hexclave.com/sdk/types/api-key Reference for the ApiKey type for programmatic backend access. `ApiKey` represents an authentication token that allows programmatic access to your application's backend. API keys can be associated with individual users or teams. On this page: * [`ApiKey`](#apikey) * Types: * [`UserApiKey`](#userapikey) * [`TeamApiKey`](#teamapikey) *** # `ApiKey` API keys provide a way for users to authenticate with your backend services without using their primary credentials. They can be created for individual users or for teams, allowing programmatic access to your application. API keys can be obtained through: * [`user.createApiKey()`](/sdk/types/user#currentusercreateapikeyoptions) * [`user.listApiKeys()`](/sdk/types/user#currentuserlistapikeys) * [`user.useApiKeys()`](/sdk/types/user#currentuseruseapikeys) (React hook) * [`team.createApiKey()`](/sdk/types/team#teamcreateapikeyoptions) * [`team.listApiKeys()`](/sdk/types/team#teamlistapikeys) * [`team.useApiKeys()`](/sdk/types/team#teamuseapikeys) (React hook) ## Table of Contents ## Properties The unique identifier for this API key. `typescript declare const id: string; ` A human-readable description of the API key's purpose. `typescript declare const description: string; ` The date and time when this API key will expire. If not set, the key does not expire. `typescript declare const expiresAt: Date | undefined; ` The date and time when this API key was manually revoked. If `null`, the key has not been revoked. May be `undefined` if the information is not available. `typescript declare const manuallyRevokedAt: Date | null | undefined; ` The date and time when this API key was created. `typescript declare const createdAt: Date; ` The full API key string on first creation; subsequently returns `{ lastFour: string }` for security. `typescript declare const value: string | { lastFour: string }; ` Whether this is a `"user"` or `"team"` API key. `typescript declare const type: "user" | "team"; ` For user API keys, the ID of the user that owns this API key. `typescript declare const userId: string | undefined; ` For team API keys, the ID of the team that owns this API key. `typescript declare const teamId: string | undefined; ` ## Methods Checks if the API key is still valid, meaning it is neither expired nor revoked. ```typescript theme={null} declare function isValid(): boolean; ``` ```typescript theme={null} if (apiKey.isValid()) { console.log("API key is still valid"); } else { console.log("API key is invalid"); } ``` Returns the reason why the API key is invalid, or `null` if it is valid. ```typescript theme={null} declare function whyInvalid(): "manually-revoked" | "expired" | null; ``` ```typescript theme={null} const reason = apiKey.whyInvalid(); if (reason) { console.log(`API key is invalid because it was ${reason}`); } else { console.log("API key is valid"); } ``` Revokes the API key, preventing it from being used for authentication. ```typescript theme={null} declare function revoke(): Promise; ``` ```typescript theme={null} await apiKey.revoke(); console.log("API key has been revoked"); ``` Updates the API key properties. A new description for the API key. A new expiration date, or `null` to remove the expiration. Set to `true` to revoke the API key. ```typescript theme={null} declare function update(options: { description?: string; expiresAt?: Date | null; revoked?: boolean; }): Promise; ``` ```typescript theme={null} await apiKey.update({ description: "Updated description", expiresAt: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000), }); ``` *** # Types ### `UserApiKey` A type alias for an API key owned by a user. ```typescript theme={null} type UserApiKey = ApiKey<"user", false>; ``` ### `UserApiKeyFirstView` A type alias for a newly created user API key, which includes the full key value instead of just the last four characters. ```typescript theme={null} type UserApiKeyFirstView = ApiKey<"user", true>; ``` ### `TeamApiKey` A type alias for an API key owned by a team. ```typescript theme={null} type TeamApiKey = ApiKey<"team", false>; ``` ### `TeamApiKeyFirstView` A type alias for a newly created team API key, which includes the full key value instead of just the last four characters. ```typescript theme={null} type TeamApiKeyFirstView = ApiKey<"team", true>; ``` *** # Creation Options When creating an API key using [`user.createApiKey()`](/sdk/types/user#currentusercreateapikeyoptions) or [`team.createApiKey()`](/sdk/types/team#teamcreateapikeyoptions), you need to provide an options object. A human-readable description of the API key's purpose. The date when the API key will expire. Use `null` for keys that don't expire. Whether the API key is public. Defaults to `false`. **Secret API Keys** are monitored by Hexclave's secret scanner, which can revoke them if detected in public code repositories. **Public API Keys** are designed for client-side code where exposure is not a concern. ```typescript theme={null} const user = await hexclaveApp.getUser(); const secretKey = await user.createApiKey({ description: "Backend integration", expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), isPublic: false, }); const publicKey = await user.createApiKey({ description: "Client-side access", expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), isPublic: true, }); ``` # ConnectedAccount Source: https://docs.hexclave.com/sdk/types/connected-account Reference for ConnectedAccount types for OAuth connections. `OAuthConnection` represents an OAuth connection to an external provider (like Google, GitHub, etc.) that is linked to a user. You can use connected accounts to access the user's data on those platforms, such as reading Google Drive files or sending emails via Gmail. For a guide on how to use connected accounts, see the [Connected Accounts guide](/guides/apps/authentication/connected-accounts). On this page: * [`Connection`](#connection) * [`OAuthConnection`](#oauthconnection) # `Connection` Basic information about a connected account. This is the base type that `OAuthConnection` extends. ## Table of Contents ## Properties The provider config ID. Same as `provider` and exists for backward compatibility. Deprecated: Use `provider` instead. `typescript declare const id: string; ` The provider config ID (for example, `"google"` or `"github"`). `typescript declare const provider: string; ` The account ID from the OAuth provider, such as the Google user ID. `typescript declare const providerAccountId: string; ` *** # `OAuthConnection` Extends `Connection` with methods to retrieve OAuth access tokens. Get it with: * [`user.getConnectedAccount({ provider, providerAccountId })`](/sdk/types/user#currentusergetconnectedaccount) * [`user.useConnectedAccount({ provider, providerAccountId })`](/sdk/types/user#currentuseruseconnectedaccount) (React hook) * [`user.listConnectedAccounts()`](/sdk/types/user#currentuserlistconnectedaccounts) * [`user.useConnectedAccounts()`](/sdk/types/user#currentuseruseconnectedaccounts) (React hook) * [`user.getOrLinkConnectedAccount(provider)`](/sdk/types/user#currentusergetorlinkconnectedaccount) * [`user.useOrLinkConnectedAccount(provider)`](/sdk/types/user#currentuseruseorlinkconnectedaccount) (React hook) ## Table of Contents ## Methods Gets an OAuth access token for this connected account. The token can be used to call the provider's APIs on the user's behalf. Returns a `Result` object: * On success: `{ status: "ok", data: { accessToken: string } }` * On error: `{ status: "error", error: OAuthAccessTokenNotAvailable }` if the refresh token has been revoked or expired, or if the requested scopes are not available. If provided, only returns a token that has all of these scopes. If the current token does not have the required scopes, the result will be an error. ```typescript theme={null} declare function getAccessToken(options?: { scopes?: string[]; }): Promise>; ``` ```typescript theme={null} const result = await account.getAccessToken(); if (result.status === "ok") { const { accessToken } = result.data; } ``` ```typescript theme={null} const result = await account.getAccessToken({ scopes: ["https://www.googleapis.com/auth/drive.readonly"], }); ``` React hook version of `getAccessToken`. Returns the access token result reactively. If provided, only returns a token that has all of these scopes. ```typescript theme={null} declare function useAccessToken(options?: { scopes?: string[]; }): Result<{ accessToken: string }, OAuthAccessTokenNotAvailable>; ``` ```tsx theme={null} function MyComponent() { const user = useUser({ or: "redirect" }); const accounts = user.useConnectedAccounts(); const googleAccount = accounts.find( (account) => account.provider === "google", ); const result = googleAccount?.useAccessToken(); if (result?.status === "ok") { return
    Token: {result.data.accessToken}
    ; } return
    No Google token available
    ; } ```
    For practical usage instructions, see the [Connected Accounts guide](/guides/apps/authentication/connected-accounts). # ContactChannel Source: https://docs.hexclave.com/sdk/types/contact-channel Reference for ContactChannel and ServerContactChannel types for user contact information. `ContactChannel` represents a user's contact information, such as an email address or phone number. Some auth methods, like OTP/magic link or password, use contact channels for authentication. On this page: * [`ContactChannel`](#contactchannel) * [`ServerContactChannel`](#servercontactchannel) # `ContactChannel` Basic information about a contact channel, as seen by a user themselves. Usually obtained by calling [`user.listContactChannels()`](/sdk/types/user#currentuserlistcontactchannels) or [`user.useContactChannels()`](/sdk/types/user#currentuserusecontactchannels). ## Table of Contents ## Properties The ID of the contact channel. `typescript declare const id: string; ` The value of the contact channel. If the type is `"email"`, this is an email address. `typescript declare const value: string; ` The type of contact channel. Currently this is always `"email"`. `typescript declare const type: "email"; ` Whether the contact channel is the user's primary contact method. If an email is set to primary, it will be the value on `user.primaryEmail`. `typescript declare const isPrimary: boolean; ` Whether the contact channel is verified. `typescript declare const isVerified: boolean; ` Whether the contact channel is used for authentication. If `true`, the user can use this contact channel with OTP or password to sign in. `typescript declare const usedForAuth: boolean; ` ## Methods Sends a verification email to this contact channel. Once the user clicks the verification link in the email, the contact channel will be marked as verified. The URL to redirect to after the user clicks the verification link. ```typescript theme={null} declare function sendVerificationEmail(options?: { callbackUrl?: string; }): Promise; ``` ```typescript theme={null} await contactChannel.sendVerificationEmail(); ``` Updates the contact channel. After updating the value, the contact channel will be marked as unverified. The new value of the contact channel. Whether the contact channel is used for authentication. Whether the contact channel is the user's primary contact channel. ```typescript theme={null} declare function update(options: { value?: string; usedForAuth?: boolean; isPrimary?: boolean; }): Promise; ``` ```typescript theme={null} await contactChannel.update({ value: "new-email@example.com", usedForAuth: true, }); ``` Deletes the contact channel. ```typescript theme={null} declare function delete(): Promise; ``` ```typescript theme={null} await contactChannel.delete(); ``` *** # `ServerContactChannel` Like `ContactChannel`, but includes additional methods and properties that require the `SECRET_SERVER_KEY`. Usually obtained by calling [`serverUser.listContactChannels()`](/sdk/types/user#serveruserlistcontactchannels) or [`serverUser.useContactChannels()`](/sdk/types/user#serveruserusecontactchannels). ## Table of Contents ## Methods Updates the contact channel. This method is similar to the one on `ContactChannel`, but also allows setting the `isVerified` property. The new value of the contact channel. Whether the contact channel is used for authentication. Whether the contact channel is verified. Whether the contact channel is the user's primary contact channel. ```typescript theme={null} declare function update(options: { value?: string; usedForAuth?: boolean; isVerified?: boolean; isPrimary?: boolean; }): Promise; ``` ```typescript theme={null} await serverContactChannel.update({ value: "new-email@example.com", usedForAuth: true, isVerified: true, }); ``` # Customer Source: https://docs.hexclave.com/sdk/types/customer Reference for the Customer interface providing payment and item management capabilities. The `Customer` interface provides payment and item management functionality that is shared between users and teams. Both [`CurrentUser`](/sdk/types/user#currentuser) and [`Team`](/sdk/types/team#team) types extend this interface, allowing them to create checkout URLs and manage items. This interface is automatically available on: * [`CurrentUser`](/sdk/types/user#currentuser) objects * [`Team`](/sdk/types/team#team) objects * [`ServerUser`](/sdk/types/user#serveruser) objects (with additional server-side capabilities) * [`ServerTeam`](/sdk/types/team#serverteam) objects (with additional server-side capabilities) ## Table of Contents ## Properties Unique identifier - the user ID for individual accounts or team ID for organizations. `typescript declare const id: string; ` ## Methods Creates a secure checkout URL for purchasing a product. This method integrates with Stripe to generate a payment link that handles the entire purchase flow. The ID of the product to purchase, as configured in your Hexclave project settings. The URL to redirect the user to after they complete (or cancel) the checkout. If not provided, the user is redirected to the current page. A secure URL that redirects to the Stripe checkout page. ```typescript theme={null} declare function createCheckoutUrl(options: { productId: string; returnUrl?: string; }): Promise; ``` ```typescript theme={null} const user = useUser({ or: "redirect" }); const handleUpgrade = async () => { const checkoutUrl = await user.createCheckoutUrl({ productId: "prod_premium_monthly", returnUrl: "https://myapp.com/billing", }); window.location.href = checkoutUrl; }; ``` ```typescript theme={null} const team = await user.getTeam("team_123"); const purchaseSeats = async () => { const checkoutUrl = await team.createCheckoutUrl({ productId: "prod_additional_seats", }); window.open(checkoutUrl, "_blank"); }; ``` Creates a Stripe SetupIntent for adding a new payment method. The returned `clientSecret` and `stripeAccountId` can be used with Stripe.js to collect payment details. ```typescript theme={null} declare function createPaymentMethodSetupIntent(): Promise<{ clientSecret: string; stripeAccountId: string; }>; ``` ```typescript theme={null} const setupIntent = await user.createPaymentMethodSetupIntent(); // Use setupIntent.clientSecret with Stripe.js ``` Sets the default payment method from a completed SetupIntent. Call this after the user has successfully added a payment method through Stripe.js. The ID of the completed SetupIntent. ```typescript theme={null} declare function setDefaultPaymentMethodFromSetupIntent( setupIntentId: string ): Promise; ``` ```typescript theme={null} const paymentMethod = await user.setDefaultPaymentMethodFromSetupIntent( "seti_1abc123" ); console.log(`Set default card ending in ${paymentMethod?.last4}`); ``` Switches an existing subscription from one product to another. Useful for upgrading or downgrading plans. The product ID of the current subscription to switch from. The product ID to switch to. The specific price ID to use for the new product. The quantity for the new subscription. ```typescript theme={null} declare function switchSubscription(options: { fromProductId: string; toProductId: string; priceId?: string; quantity?: number; }): Promise; ``` ```typescript theme={null} await user.switchSubscription({ fromProductId: "prod_basic_monthly", toProductId: "prod_premium_monthly", }); ``` Retrieves the customer's billing information, including whether they have a Stripe customer record and their default payment method. ```typescript theme={null} declare function getBilling(): Promise; ``` ```typescript theme={null} const billing = await user.getBilling(); if (billing.defaultPaymentMethod) { console.log(`Card ending in ${billing.defaultPaymentMethod.last4}`); } ``` React hook version of `getBilling`. Returns the customer's billing information reactively. ```typescript theme={null} declare function useBilling(): CustomerBilling; ``` ```tsx theme={null} function BillingInfo() { const user = useUser({ or: "redirect" }); const billing = user.useBilling(); return (
    {billing.defaultPaymentMethod ? `Card ending in ${billing.defaultPaymentMethod.last4}` : "No payment method on file"}
    ); } ```
    Retrieves information about a specific item associated with this customer. The ID of the item to retrieve. An [`Item`](/sdk/types/item#item) object containing the display name, current quantity, and other details. ```typescript theme={null} declare function getItem(itemId: string): Promise; ``` ```typescript theme={null} const credits = await user.getItem("credits"); console.log(`Available credits: ${credits.nonNegativeQuantity}`); console.log(`Actual balance: ${credits.quantity}`); ``` React hook version of `getItem`. Provides real-time updates when the item quantity changes. The ID of the item to retrieve. ```typescript theme={null} declare function useItem(itemId: string): Item; ``` ```tsx theme={null} function CreditsWidget() { const user = useUser({ or: "redirect" }); const credits = user.useItem("credits"); return (

    Available Credits

    {credits.nonNegativeQuantity}
    {credits.displayName}
    ); } ```
    Lists the products owned by this customer, including subscription status and quantities. Pagination cursor from a previous response's `nextCursor`. Maximum number of products to return. ```typescript theme={null} declare function listProducts(options?: { cursor?: string; limit?: number; }): Promise; ``` ```typescript theme={null} const products = await user.listProducts(); for (const product of products) { console.log(`${product.displayName}: ${product.quantity}`); } ``` React hook version of `listProducts`. Returns the customer's products reactively. Pagination cursor from a previous response's `nextCursor`. Maximum number of products to return. ```typescript theme={null} declare function useProducts(options?: { cursor?: string; limit?: number; }): CustomerProductsList; ``` Lists the invoices for this customer. Pagination cursor from a previous response's `nextCursor`. Maximum number of invoices to return. ```typescript theme={null} declare function listInvoices(options?: { cursor?: string; limit?: number; }): Promise; ``` ```typescript theme={null} const invoices = await user.listInvoices(); for (const invoice of invoices) { console.log(`${invoice.status}: $${invoice.amountTotal / 100}`); } ``` React hook version of `listInvoices`. Returns the customer's invoices reactively. Pagination cursor from a previous response's `nextCursor`. Maximum number of invoices to return. ```typescript theme={null} declare function useInvoices(options?: { cursor?: string; limit?: number; }): CustomerInvoicesList; ``` *** # Related Types These types are returned by the methods above. You'll encounter them when working with billing, products, and invoices. ## `CustomerBilling` Returned by [`getBilling()`](#customergetbilling) / [`useBilling()`](#customerusebilling). Whether the customer has a Stripe customer record. `typescript declare const hasCustomer: boolean; ` The customer's default payment method, or `null` if none is set. When present, contains `id`, `brand`, `last4`, `exp_month`, and `exp_year`. ```typescript theme={null} declare const defaultPaymentMethod: { id: string; brand: string | null; last4: string | null; exp_month: number | null; exp_year: number | null; } | null; ``` ## `CustomerProduct` Returned by [`listProducts()`](#customerlistproducts) / [`useProducts()`](#customeruseproducts). The return value is an array of `CustomerProduct` with a `nextCursor: string | null` property for pagination. The product ID, or `null` for inline products. `typescript declare const id: string | null; ` The human-readable name of the product. `typescript declare const displayName: string; ` How many of this product the customer owns. `typescript declare const quantity: number; ` Whether this is a one-time purchase or a recurring subscription. `typescript declare const type: "one_time" | "subscription"; ` Subscription details, or `null` for one-time purchases. Includes the subscription ID, current period end date, and cancellation status. ```typescript theme={null} declare const subscription: null | { subscriptionId: string | null; currentPeriodEnd: Date | null; cancelAtPeriodEnd: boolean; isCancelable: boolean; }; ``` The type of customer that owns this product. `typescript declare const customerType: "user" | "team" | "custom"; ` Whether this product can only be granted server-side. `typescript declare const isServerOnly: boolean; ` Whether multiple quantities of this product can be stacked. `typescript declare const stackable: boolean; ` Available products to switch this subscription to. Only present for subscription products. ```typescript theme={null} declare const switchOptions: Array<{ productId: string; displayName: string; prices: InlineProduct["prices"]; }> | undefined; ``` ## `CustomerInvoice` Returned by [`listInvoices()`](#customerlistinvoices) / [`useInvoices()`](#customeruseinvoices). The return value is an array of `CustomerInvoice` with a `nextCursor: string | null` property for pagination. When the invoice was created. `typescript declare const createdAt: Date; ` The invoice status. `typescript declare const status: "draft" | "open" | "paid" | "uncollectible" | "void" | null; ` The total amount of the invoice in the smallest currency unit (e.g. cents). `typescript declare const amountTotal: number; ` A URL to the hosted invoice page on Stripe, or `null` if not available. `typescript declare const hostedInvoiceUrl: string | null; ` *** ## Payment Workflow Call `createCheckoutUrl()` with the desired product ID. Direct the user to the returned URL. Stripe handles the payment process on their hosted page. Hexclave receives webhook notifications from Stripe. Purchased items are automatically added to the customer's account. The user is redirected back to your application. ## Usage Notes * **Purchases**: When a user completes a purchase, associated items are automatically added * **Subscriptions**: Recurring subscriptions automatically replenish items at the specified intervals * **Manual allocation**: Server-side code can manually adjust item quantities using [`ServerItem`](/sdk/types/item#serveritem) methods * **Race conditions**: Use [`tryDecreaseQuantity()`](/sdk/types/item#serveritemtrydecreasequantity) for atomic, race-condition-free item consumption # SendEmailOptions Source: https://docs.hexclave.com/sdk/types/email Reference for the SendEmailOptions type used with the sendEmail method. This is a detailed reference for email-related types in Hexclave. If you're looking for a more high-level overview, please refer to our [guide on the email system](/guides/apps/emails/overview). # `SendEmailOptions` Options for sending emails via the `sendEmail` method on `HexclaveServerApp`. ## Table of Contents ## Recipients You must provide exactly one of `userIds` or `allUsers`. An array of user IDs that will receive the email. All users must exist in your Hexclave project. Cannot be used together with `allUsers`. `typescript declare const userIds: string[]; ` Set to `true` to send the email to all users in the project. Cannot be used together with `userIds`. `typescript declare const allUsers: true; ` ## Content You must provide exactly one of `html`, `templateId`, or `draftId`. Custom HTML content for the email. Cannot be used together with `templateId` or `draftId`. `typescript declare const html: string; ` ID of the email template to use. Cannot be used together with `html` or `draftId`. `typescript declare const templateId: string; ` ID of an email draft to send. Cannot be used together with `html` or `templateId`. `typescript declare const draftId: string; ` ## Options A string ID for a custom theme, `null` for no theme, or `false` to use the default theme. `typescript declare const themeId: string | null | false | undefined; ` Optional email subject line. If you use a template, this overrides the template's default subject. `typescript declare const subject: string | undefined; ` Optional notification category name for user preferences. Users can opt in or out of specific categories through their account settings. `typescript declare const notificationCategoryName: string | undefined; ` Optional date to schedule the email for. If not provided, the email is sent immediately. `typescript declare const scheduledAt: Date | undefined; ` Optional variables to substitute in the template. Only used when `templateId` is provided. `typescript declare const variables: Record | undefined; ` ## Usage Choose a content method (`html`, `templateId`, or `draftId`) and a recipient method (`userIds` or `allUsers`): Provide custom HTML directly: ```typescript theme={null} await hexclaveServerApp.sendEmail({ userIds: ["user-id-1"], subject: "Hello!", html: "

    Welcome

    Thanks for signing up!

    ", }); ```
    Use a template with variable substitution: ```typescript theme={null} await hexclaveServerApp.sendEmail({ userIds: ["user-id-1"], templateId: "welcome-template", variables: { userName: "John Doe", activationUrl: "https://app.com/activate/token123", }, }); ``` Send from a saved email draft: ```typescript theme={null} await hexclaveServerApp.sendEmail({ allUsers: true, draftId: "my-draft-id", }); ``` Schedule an email for later delivery: ```typescript theme={null} await hexclaveServerApp.sendEmail({ userIds: ["user-id-1"], templateId: "weekly-digest", scheduledAt: new Date("2025-01-15T09:00:00Z"), }); ```
    The `notificationCategoryName`, `themeId`, `scheduledAt`, and `variables` properties work independently with any content method. # Item Source: https://docs.hexclave.com/sdk/types/item Reference for Item and ServerItem types for managing quantifiable resources. Items represent quantifiable resources in your application, such as credits, API calls, storage quotas, or subscription allowances. They can be associated with users, teams, or custom customers and are managed through Hexclave's payment system. On this page: * [Item](#item) * [ServerItem](#serveritem) *** # `Item` The `Item` type represents a quantifiable resource that can be consumed or managed within your application. Items are typically obtained through purchases, subscriptions, or manual allocation. Items can be retrieved through: * [`user.getItem()`](/sdk/types/user#currentusergetitem) * [`user.useItem()`](/sdk/types/user#currentuseruseitem) (React hook) * [`team.getItem()`](/sdk/types/team#teamgetitem) * [`team.useItem()`](/sdk/types/team#teamuseitem) (React hook) ## Table of Contents ## Properties The human-readable name of the item as configured in your Hexclave project settings. `typescript declare const displayName: string; ` The current quantity. This value can be negative, which is useful for tracking overdrafts or pending charges. `typescript declare const quantity: number; ` The quantity clamped to a minimum of 0. Equivalent to `Math.max(0, quantity)`. Useful for display purposes when you don't want to show negative values to users. `typescript declare const nonNegativeQuantity: number; ` *** # `ServerItem` The `ServerItem` type extends `Item` with additional server-side methods for modifying quantities. This type is only available in server-side contexts and provides race-condition-safe operations for managing item quantities. Server items can be retrieved through: * [`serverUser.getItem()`](/sdk/types/user#serverusergetitem) * [`serverUser.useItem()`](/sdk/types/user#serveruseruseitem) (React hook) * [`serverTeam.getItem()`](/sdk/types/team#serverteamgetitem) * [`serverTeam.useItem()`](/sdk/types/team#serverteamuseitem) (React hook) ## Table of Contents ## Methods Increases the item quantity by the specified amount. This operation is atomic and safe for concurrent use. The amount to increase the quantity by. Must be a positive number. ```typescript theme={null} declare function increaseQuantity(amount: number): Promise; ``` ```typescript theme={null} const user = await hexclaveServerApp.getUser({ userId: "user_123" }); const credits = await user.getItem("credits"); await credits.increaseQuantity(100); ``` Decreases the item quantity by the specified amount. This operation allows the quantity to go negative. If you want to prevent the quantity from going below zero, use [`tryDecreaseQuantity()`](#serveritemtrydecreasequantity) instead. The amount to decrease the quantity by. Must be a positive number. ```typescript theme={null} declare function decreaseQuantity(amount: number): Promise; ``` ```typescript theme={null} const user = await hexclaveServerApp.getUser({ userId: "user_123" }); const credits = await user.getItem("credits"); await credits.decreaseQuantity(50); ``` Attempts to decrease the item quantity by the specified amount, but only if the result would be non-negative. Returns `true` if the operation succeeded, `false` if it would result in a negative quantity. This method is race-condition-safe and is ideal for implementing prepaid credit systems where you need to ensure sufficient balance before allowing an operation. The amount to decrease the quantity by. Must be a positive number. `true` if the quantity was successfully decreased, `false` if the operation would result in a negative quantity. ```typescript theme={null} declare function tryDecreaseQuantity(amount: number): Promise; ``` ```typescript theme={null} const user = await hexclaveServerApp.getUser({ userId: "user_123" }); const credits = await user.getItem("credits"); const success = await credits.tryDecreaseQuantity(50); if (success) { console.log("Credits consumed successfully"); } else { console.log("Insufficient credits"); throw new Error("Not enough credits available"); } ``` The `tryDecreaseQuantity()` method is particularly valuable for prepaid credit systems where operations require sufficient balance verification before proceeding. # Project Source: https://docs.hexclave.com/sdk/types/project Reference for the Project type representing project configuration. The `Project` object contains the information and configuration of a project, such as the name, description, and enabled authentication methods. Each [`HexclaveApp`](/sdk/objects/hexclave-app) corresponds to a project. You can obtain its `Project` object by calling [`hexclaveApp.getProject()`](/sdk/objects/hexclave-app#stackclientappgetproject) or [`hexclaveApp.useProject()`](/sdk/objects/hexclave-app#stackclientappuseproject). ## Table of Contents ## Properties The unique ID of the project. `typescript declare const id: string; ` The display name of the project. `typescript declare const displayName: string; ` Configuration settings governing authentication and user management features. Whether sign-up is enabled for the project. Whether credential-based (email/password) authentication is enabled. Whether magic-link authentication is enabled. Whether passkey authentication is enabled. Whether client-side team creation is permitted. Whether client-side user deletion is enabled. The list of OAuth providers configured for the project. Each provider has an `id: string`. Whether user-level API key creation is allowed. Whether team-level API key creation is allowed. ```typescript theme={null} declare const config: { signUpEnabled: boolean; credentialEnabled: boolean; magicLinkEnabled: boolean; passkeyEnabled: boolean; clientTeamCreationEnabled: boolean; clientUserDeletionEnabled: boolean; oauthProviders: OAuthProviderConfig[]; allowUserApiKeys: boolean; allowTeamApiKeys: boolean; }; type OAuthProviderConfig = { id: string; }; ``` # Team Source: https://docs.hexclave.com/sdk/types/team Reference for Team and ServerTeam types for team management. This is a detailed reference for the `Team` object. If you're looking for a more high-level overview, please refer to our [guide on teams](/guides/apps/teams/overview). On this page: * [Team](#team) * [ServerTeam](#serverteam) *** # `Team` A `Team` object contains basic information and functions about a team, to the extent of which a member of the team would have access to it. You can get `Team` objects with the `user.useTeams()` or `user.listTeams()` functions. The created team will then inherit the permissions of that user; for example, the `team.update(...)` function can only succeed if the user is allowed to make updates to the team. ## Table of Contents ## Properties Unique team identifier. `typescript declare const id: string; ` The display name of the team. `typescript declare const displayName: string; ` The profile image URL of the team, or `null` if no profile image is set. `typescript declare const profileImageUrl: string | null; ` Custom metadata accessible to clients. `typescript declare const clientMetadata: Json; ` Read-only metadata for clients. `typescript declare const clientReadOnlyMetadata: Json; ` ## Methods Updates the team information. Requires the current user to have the `$update_team` permission. The fields to update on the team. The display name of the team. The profile image URL of the team. The client metadata of the team. ```typescript theme={null} declare function update(data: { displayName?: string; profileImageUrl?: string | null; clientMetadata?: Json; }): Promise; ``` ```typescript theme={null} await team.update({ displayName: "New Team Name", profileImageUrl: "https://example.com/profile.png", clientMetadata: { address: "123 Main St, Anytown, USA", }, }); ``` Deletes the team. Requires the current user to have the `$delete_team` permission. ```typescript theme={null} declare function delete(): Promise; ``` Sends an invitation email to a user to join the team. Requires the `$invite_members` permission. An invitation email containing a magic link will be sent to the specified user. If the user has an existing account, they will be automatically added to the team upon clicking the link. For users without an account, the link will guide them through the sign-up process before adding them to the team. The email of the user to invite. The URL where users will be redirected after accepting the team invitation. This is required in server environments. ```typescript theme={null} declare function inviteUser(options: { email: string; callbackUrl?: string; }): Promise; ``` ```typescript theme={null} await team.inviteUser({ email: "user@example.com", }); ``` Gets a list of users in the team. Requires the `$read_members` permission. ```typescript theme={null} declare function listUsers(): Promise; ``` ```typescript theme={null} const users = await team.listUsers(); users.forEach((user) => { console.log(user.id, user.teamProfile.displayName); }); ``` React hook version of `listUsers()`. ```typescript theme={null} declare function useUsers(): TeamUser[]; ``` Gets a list of invitations to the team. Requires the `$read_members` and `$invite_members` permissions. ```typescript theme={null} declare function listInvitations(): Promise; ``` ```typescript theme={null} const invitations = await team.listInvitations(); invitations.forEach((invitation) => { console.log(invitation.id, invitation.recipientEmail); }); ``` React hook version of `listInvitations()`. ```typescript theme={null} declare function useInvitations(): SentTeamInvitation[]; ``` ## API Keys Creates a new API key for the team. The name of the API key. The description of the API key. The expiration date of the API key. ```typescript theme={null} declare function createApiKey(options: { name: string; description?: string; expiresAt: Date; }): Promise; ``` ```typescript theme={null} await team.createApiKey({ name: "New API Key", description: "This is a new API key", expiresAt: new Date("2024-01-01"), }); ``` Gets a list of API keys for the team. ```typescript theme={null} declare function listApiKeys(): Promise; ``` React hook version of `listApiKeys()`. ```typescript theme={null} declare function useApiKeys(): TeamApiKey[]; ``` ## Billing & Payments Creates a checkout URL for the team to purchase products. Integrates with Stripe to generate a secure payment link. The ID of the product to purchase. ```typescript theme={null} declare function createCheckoutUrl(options: { productId: string; }): Promise; ``` ```typescript theme={null} const checkoutUrl = await team.createCheckoutUrl({ productId: "prod_team_seats", }); window.location.href = checkoutUrl; ``` Retrieves information about a specific item, such as credits, API quotas, or storage limits, for the team. The ID of the item to retrieve. ```typescript theme={null} declare function getItem(itemId: string): Promise; ``` ```typescript theme={null} const apiQuota = await team.getItem("api_calls"); console.log(`Team has ${apiQuota.quantity} API calls remaining`); ``` React hook version of `getItem()`. The ID of the item to retrieve. ```typescript theme={null} declare function useItem(itemId: string): Item; ``` Creates a Stripe setup intent for adding a payment method to the team. ```typescript theme={null} declare function createPaymentMethodSetupIntent(): Promise; ``` Sets the default payment method from a completed setup intent. The ID of the completed setup intent. ```typescript theme={null} declare function setDefaultPaymentMethodFromSetupIntent( setupIntentId: string, ): Promise; ``` Switches a subscription from one product to another. Subscription switch options. The product ID to switch from. The product ID to switch to. A specific price ID to use. The quantity for the new subscription. ```typescript theme={null} declare function switchSubscription(options: { fromProductId: string; toProductId: string; priceId?: string; quantity?: number; }): Promise; ``` Returns the billing information for the team. ```typescript theme={null} declare function getBilling(): Promise; ``` React hook that returns the billing information. ```typescript theme={null} declare function useBilling(): CustomerBilling; ``` Returns all products available to the team. ```typescript theme={null} declare function listProducts(options?: CustomerProductsListOptions): Promise; ``` React hook that returns all products. ```typescript theme={null} declare function useProducts(options?: CustomerProductsListOptions): CustomerProductsList; ``` Returns all invoices for the team. ```typescript theme={null} declare function listInvoices(options?: CustomerInvoicesListOptions): Promise; ``` React hook that returns all invoices. ```typescript theme={null} declare function useInvoices(options?: CustomerInvoicesListOptions): CustomerInvoicesList; ``` *** # `ServerTeam` Like [`Team`](#team), but with server permissions through [`HexclaveServerApp`](/sdk/objects/hexclave-app#hexclaveserverapp). Has full read and write access to everything. Calling `serverUser.getTeam(...)` and `serverUser.listTeams()` will return `ServerTeam` objects if the user is a [`ServerUser`](/sdk/types/user#serveruser). Alternatively, you can call `hexclaveServerApp.getTeam('team_id_123')` or `hexclaveServerApp.listTeams()` to query all teams of the project. `ServerTeam` extends the `Team` object, providing additional functions and properties. While the `Team` object's functions may require specific user permissions, the corresponding functions in `ServerTeam` can be executed without these permission checks. ## Table of Contents ## Additional Properties The date and time when the team was created. `typescript declare const createdAt: Date; ` Server-only metadata. `typescript declare const serverMetadata: Json; ` ## Additional Methods Extended update supporting server-only fields. Does not require any permissions. The fields to update on the team. The display name of the team. The profile image URL of the team. Client metadata. Client read-only metadata. Server-only metadata. ```typescript theme={null} declare function update(data: ServerTeamUpdateOptions): Promise; ``` ```typescript theme={null} await serverTeam.update({ displayName: "Updated Team Name", serverMetadata: { internalTag: "enterprise" }, }); ``` Gets a list of users in the team. Returns `ServerTeamUser` objects and does not require any permissions. ```typescript theme={null} declare function listUsers(): Promise; ``` ```typescript theme={null} const users = await team.listUsers(); users.forEach((user) => { console.log(user.id, user.teamProfile.displayName); }); ``` React hook version of `listUsers()`. ```typescript theme={null} declare function useUsers(): ServerTeamUser[]; ``` Adds a user to the team directly without sending an invitation email. The ID of the user to add. ```typescript theme={null} declare function addUser(userId: string): Promise; ``` ```typescript theme={null} await team.addUser("user_id_123"); ``` Removes a user from the team. The ID of the user to remove. ```typescript theme={null} declare function removeUser(userId: string): Promise; ``` ```typescript theme={null} await team.removeUser("user_id_123"); ``` *** # Types ### `TeamCreateOptions` Options for creating a team via [`user.createTeam()`](/sdk/types/user#currentusercreateteam). The display name for the team. The profile image URL for the team. ### `ServerTeamCreateOptions` Extended creation options available on the server. The display name for the team. The profile image URL for the team. The user ID of the team creator. If not set, the team is created without a creator. ### `TeamUpdateOptions` Options for updating a team via [`team.update()`](#teamupdate). The display name of the team. The profile image URL of the team. Client metadata. ### `ServerTeamUpdateOptions` Extended update options available on the server via [`serverTeam.update()`](#serverteamupdate). The display name of the team. The profile image URL of the team. Client metadata. Client read-only metadata. Server-only metadata. *** # Invitation Types ### `SentTeamInvitation` Represents an invitation that was sent from a team. Returned by [`team.listInvitations()`](#teamlistinvitations). The unique identifier for the invitation. The email address of the invited user, or `null` if not specified. The date and time when the invitation expires. ```typescript theme={null} declare function revoke(): Promise; ``` The `revoke()` method cancels the invitation. ### `ReceivedTeamInvitation` Represents an invitation received by a user. Returned by [`user.listTeamInvitations()`](/sdk/types/user#currentuserlistteaminvitations). The unique identifier for the invitation. The ID of the team that sent the invitation. The display name of the inviting team. The email address the invitation was sent to. The date and time when the invitation expires. ```typescript theme={null} declare function accept(): Promise; ``` The `accept()` method accepts the invitation and adds the user to the team. # TeamPermission Source: https://docs.hexclave.com/sdk/types/team-permission Reference for the TeamPermission type representing permissions within a team. The `TeamPermission` object represents a permission that a user has within a team. Currently, it contains only an `id` to specify the permission. You can get `TeamPermission` objects by calling functions such as `user.getPermission(...)` or `user.listPermissions()`. ## Table of Contents ## Properties The identifier of the permission. `typescript declare const id: string; ` ## Access `TeamPermission` objects can be obtained through methods like `user.getPermission(...)` or `user.listPermissions()`. See [User permissions methods](/sdk/types/user#permissions) for full details on retrieving and checking permissions. # TeamMemberProfile Source: https://docs.hexclave.com/sdk/types/team-profile Reference for TeamMemberProfile and ServerTeamMemberProfile types for team-specific user profiles. This is a detailed reference for the `TeamMemberProfile` and `ServerTeamMemberProfile` objects. On this page: * [TeamMemberProfile](#teammemberprofile) * [ServerTeamMemberProfile](#serverteammemberprofile) # `TeamMemberProfile` The `TeamMemberProfile` object represents the profile of a user within the context of a team. It includes the user's profile information specific to the team and can be accessed through the `teamUser.teamProfile` property on a `TeamUser` object. ## Table of Contents ## Properties The display name of the user within the team context, or `null` if no display name is set. `typescript declare const displayName: string | null; ` The profile image URL of the user within the team context, or `null` if no profile image is set. `typescript declare const profileImageUrl: string | null; ` *** # `ServerTeamMemberProfile` The `ServerTeamMemberProfile` object is currently the same as `TeamMemberProfile`. These are team-specific profile attributes, distinct from general user profile data. Users may have different display names and profile images within different teams. # TeamUser Source: https://docs.hexclave.com/sdk/types/team-user Reference for TeamUser and ServerTeamUser types representing team members. On this page: * [TeamUser](#teamuser) * [ServerTeamUser](#serverteamuser) *** # `TeamUser` The `TeamUser` object is used on the client side to represent a user in the context of a team, providing minimal information about the user, including their ID and team-specific profile. It is usually obtained by calling `team.useUsers()` or `team.listUsers()` on a [`Team` object](/sdk/types/team#team). ## Table of Contents ## Properties The user's identifier. `typescript declare const id: string; ` Team-specific profile information. `typescript declare const teamProfile: TeamMemberProfile; ` *** # `ServerTeamUser` The `ServerTeamUser` object is used on the server side to represent a user within a team. Besides the team profile, it also includes all the functionality of a [`ServerUser`](/sdk/types/user#serveruser). It is usually obtained by calling `serverTeam.listUsers()` on a [`ServerTeam` object](/sdk/types/team#serverteam). ## Table of Contents ## Properties Server-side team-specific profile information. `typescript declare const teamProfile: ServerTeamMemberProfile; ` In addition to the above, `ServerTeamUser` inherits all properties and methods from [ServerUser](/sdk/types/user#serveruser). # User Source: https://docs.hexclave.com/sdk/types/user Reference for CurrentUser, ServerUser, and CurrentServerUser types. This is a detailed reference for the `User` object. If you're looking for a more high-level overview, please refer to our guide on users [here](/guides/getting-started/user-fundamentals). On this page: * [CurrentUser](#currentuser) * [ServerUser](#serveruser) * [CurrentServerUser](#currentserveruser) # `CurrentUser` Use `useUser()` to get `CurrentUser` (client). Use `hexclaveServerApp.getUser()` to get `CurrentServerUser` (server). ## Table of Contents ## Properties The user ID. This is the unique identifier of the user. `typescript declare const id: string; ` The display name of the user, or `null` if not set. The user can modify this value. `typescript declare const displayName: string | null; ` The primary email of the user. Note that this is not necessarily unique. `typescript declare const primaryEmail: string | null; ` Whether the primary email of the user is verified. `typescript declare const primaryEmailVerified: boolean; ` The profile image URL of the user, or `null` if no profile image is set. `typescript declare const profileImageUrl: string | null; ` The date and time when the user signed up. `typescript declare const signedUpAt: Date; ` Whether the user has a password set. `typescript declare const hasPassword: boolean; ` Client-visible custom metadata. This should not contain sensitive or server-only information. `typescript declare const clientMetadata: Json; ` Read-only metadata visible on the client side. It can only be modified on the server side. `typescript declare const clientReadOnlyMetadata: Json; ` The currently selected team for the user, or `null` if no team is selected. `typescript declare const selectedTeam: Team | null; ` The current session object, which provides access to authentication tokens. ```typescript theme={null} declare const currentSession: { getTokens(): Promise<{ accessToken: string | null; refreshToken: string | null }>; useTokens(): { accessToken: string | null; refreshToken: string | null }; }; ``` Whether OTP (one-time password) authentication is enabled for this user. `typescript declare const otpAuthEnabled: boolean; ` Whether passkey authentication is enabled for this user. `typescript declare const passkeyAuthEnabled: boolean; ` Whether multi-factor authentication is required for this user. `typescript declare const isMultiFactorRequired: boolean; ` Whether this is an anonymous user (signed in without credentials). `typescript declare const isAnonymous: boolean; ` Whether the user is restricted from performing certain actions. `typescript declare const isRestricted: boolean; ` The reason why the user is restricted, or `null` if the user is not restricted. ```typescript theme={null} declare const restrictedReason: { type: "anonymous" | "email_not_verified" | "restricted_by_administrator"; } | null; ``` ## Profile Management Updates multiple fields of the user at once. The fields to update on the user. The new display name for the user. Custom metadata visible to the client. The ID of the team to set as selected, or `null` to clear selection. The URL of the user's new profile image, or `null` to remove it. The TOTP multi-factor authentication secret, or `null` to disable. Whether to enable OTP authentication. Whether to enable passkey authentication. The new primary email address. ```typescript theme={null} declare function update(data: { displayName?: string | null; clientMetadata?: ReadonlyJson; selectedTeamId?: string | null; profileImageUrl?: string | null; totpMultiFactorSecret?: Uint8Array | null; otpAuthEnabled?: boolean; passkeyAuthEnabled?: boolean; primaryEmail?: string | null; }): Promise; ``` ```typescript theme={null} await user.update({ displayName: "New Display Name", clientMetadata: { address: "123 Main St", }, }); ``` Sets the display name of the user. The new display name, or `null` to clear it. ```typescript theme={null} declare function setDisplayName(displayName: string | null): Promise; ``` ```typescript theme={null} await user.setDisplayName("John Doe"); ``` Sets the client metadata for the user. The new client metadata value. ```typescript theme={null} declare function setClientMetadata(metadata: any): Promise; ``` ```typescript theme={null} await user.setClientMetadata({ theme: "dark", locale: "en" }); ``` Changes password with old and new password validation. Password change inputs. The user's current password. The new password to set. ```typescript theme={null} declare function updatePassword(data: { oldPassword: string; newPassword: string; }): Promise; ``` ```typescript theme={null} await user.updatePassword({ oldPassword: "old-password", newPassword: "new-password", }); ``` Sets a password for the user without requiring the current password. The new password to set. ```typescript theme={null} declare function setPassword(options: { password: string; }): Promise; ``` ```typescript theme={null} await user.setPassword({ password: "new-secure-password" }); ``` Converts the user object to the format expected by the Hexclave API. Useful for serializing user data in API responses or caching. ```typescript theme={null} declare function toClientJson(): CurrentUserCrud["Client"]["Read"]; ``` ```typescript theme={null} const user = useUser(); const json = user.toClientJson(); localStorage.setItem("cachedUser", JSON.stringify(json)); ``` ## Authentication Returns the HTTP `Authorization` header value in `Bearer stackauth_...` format for authenticated requests. Returns `null` if the user is not signed in. ```typescript theme={null} declare function getAuthorizationHeader(): Promise; ``` React hook that returns the HTTP `Authorization` header value in `Bearer stackauth_...` format. Returns `null` if the user is not signed in. ```typescript theme={null} declare function useAuthorizationHeader(): string | null; ``` **Deprecated:** Use `getAuthorizationHeader()` instead. Returns legacy `x-stack-auth` headers for cross-origin authenticated requests. ```typescript theme={null} declare function getAuthHeaders(): Promise>; ``` **Deprecated:** Use `useAuthorizationHeader()` instead. React hook that returns legacy `x-stack-auth` headers for cross-origin requests. ```typescript theme={null} declare function useAuthHeaders(): Record; ``` Returns the current access token, or `null` if the user is not signed in. The access token is a short-lived JWT that is automatically refreshed when it expires. ```typescript theme={null} declare function getAccessToken(): Promise; ``` React hook that returns the current access token, or `null` if not signed in. ```typescript theme={null} declare function useAccessToken(): string | null; ``` Returns the current refresh token, or `null` if the user is not signed in. The refresh token is a long-lived token used to obtain new access tokens. ```typescript theme={null} declare function getRefreshToken(): Promise; ``` React hook that returns the current refresh token, or `null` if not signed in. ```typescript theme={null} declare function useRefreshToken(): string | null; ``` Signs out the user with an optional redirect URL. URL to redirect to after sign-out. ```typescript theme={null} declare function signOut(options?: { redirectUrl?: string; }): Promise; ``` Permanently deletes the user account. Requires client-side deletion to be enabled in the project settings. ```typescript theme={null} declare function delete(): Promise; ``` ## Team Management Gets the team with the specified ID. The ID of the team to get. The team object, or `null` if the team is not found or the user is not a member. ```typescript theme={null} declare function getTeam(id: string): Promise; ``` ```typescript theme={null} const team = await user.getTeam("teamId"); ``` React hook version of `getTeam`. The ID of the team to get. ```typescript theme={null} declare function useTeam(id: string): Team | null; ``` Lists all the teams the user is a member of. ```typescript theme={null} declare function listTeams(): Promise; ``` ```typescript theme={null} const teams = await user.listTeams(); ``` React hook version of `listTeams`. ```typescript theme={null} declare function useTeams(): Team[]; ``` Sets the currently selected team for the user. The team to set as selected, or `null` to clear selection. ```typescript theme={null} declare function setSelectedTeam(team: Team | null): Promise; ``` ```typescript theme={null} const team = await user.getTeam("team_id_123"); await user.setSelectedTeam(team); ``` Creates a new team for the user. The user will be added to the team and given creator permissions. Team creation options. The display name for the team. The URL of the team's profile image. If client-side team creation is disabled in the Stack dashboard, this will throw an error. ```typescript theme={null} declare function createTeam(data: { displayName?: string; profileImageUrl?: string | null; }): Promise; ``` Removes the user from the specified team. The team to leave. ```typescript theme={null} declare function leaveTeam(team: Team): Promise; ``` Retrieves the user's profile within a specific team. The team to get the profile for. ```typescript theme={null} declare function getTeamProfile(team: Team): Promise; ``` React hook version of `getTeamProfile`. The team to get the profile for. ```typescript theme={null} declare function useTeamProfile(team: Team): EditableTeamMemberProfile; ``` Lists all pending team invitations sent to any of the current user's verified email addresses. ```typescript theme={null} declare function listTeamInvitations(): Promise; ``` React hook version of `listTeamInvitations`. Automatically re-renders when invitations change. ```typescript theme={null} declare function useTeamInvitations(): ReceivedTeamInvitation[]; ``` ## Permissions Checks if the user has a specific permission within a team. The team scope. The permission identifier. ```typescript theme={null} declare function hasPermission(scope: Team, permissionId: string): Promise; ``` Retrieves a specific permission with optional recursive lookup. The team scope. The permission identifier. Whether to look up permissions recursively. ```typescript theme={null} declare function getPermission( scope: Team, permissionId: string, options?: { recursive?: boolean }, ): Promise; ``` React hook version of `getPermission`. The team scope. The permission identifier. Whether to look up permissions recursively. ```typescript theme={null} declare function usePermission( scope: Team, permissionId: string, options?: { recursive?: boolean }, ): TeamPermission | null; ``` Lists all permissions for a team. The team scope. Whether to list permissions recursively. ```typescript theme={null} declare function listPermissions( scope: Team, options?: { recursive?: boolean }, ): Promise; ``` React hook version of `listPermissions`. The team scope. Whether to list permissions recursively. ```typescript theme={null} declare function usePermissions( scope: Team, options?: { recursive?: boolean }, ): TeamPermission[]; ``` ## Contact Channels Lists the user's contact channels. ```typescript theme={null} declare function listContactChannels(): Promise; ``` React hook version of `listContactChannels`. ```typescript theme={null} declare function useContactChannels(): ContactChannel[]; ``` Creates a new contact channel for the user. Contact channel creation options. The value of the contact channel (e.g., email address). The type of contact channel. Currently always `"email"`. Whether this contact channel can be used for authentication. Whether this should be set as the user's primary contact channel. ```typescript theme={null} declare function createContactChannel(data: { value: string; type: "email"; usedForAuth: boolean; isPrimary?: boolean; }): Promise; ``` ```typescript theme={null} const channel = await user.createContactChannel({ type: "email", value: "backup@example.com", usedForAuth: true, }); ``` Lists all notification categories. ```typescript theme={null} declare function listNotificationCategories(): Promise; ``` React hook to get all notification categories. ```typescript theme={null} declare function useNotificationCategories(): NotificationCategory[]; ``` ## OAuth Connections Lists all connected accounts. ```typescript theme={null} declare function listConnectedAccounts(): Promise; ``` React hook version of `listConnectedAccounts`. ```typescript theme={null} declare function useConnectedAccounts(): OAuthConnection[]; ``` Initiates an OAuth flow to link a new account. The OAuth provider. Requested scopes. ```typescript theme={null} declare function linkConnectedAccount( provider: string, options?: { scopes?: string[] }, ): Promise; ``` ```typescript theme={null} await user.linkConnectedAccount("google", { scopes: ["email", "profile"], }); ``` Gets an existing connected account or redirects to link if it is missing or has insufficient scopes. The OAuth provider. Requested scopes. ```typescript theme={null} declare function getOrLinkConnectedAccount( provider: string, options?: { scopes?: string[] }, ): Promise; ``` ```typescript theme={null} const googleConnection = await user.getOrLinkConnectedAccount("google", { scopes: ["email", "profile"], }); ``` React hook version of `getOrLinkConnectedAccount`. The OAuth provider. Requested scopes. ```typescript theme={null} declare function useOrLinkConnectedAccount( provider: string, options?: { scopes?: string[] }, ): OAuthConnection; ``` Gets a specific OAuth provider by ID. The OAuth provider ID. ```typescript theme={null} declare function getOAuthProvider(id: string): Promise; ``` React hook to get a specific OAuth provider by ID. The OAuth provider ID. ```typescript theme={null} declare function useOAuthProvider(id: string): OAuthProvider | null; ``` Lists all OAuth providers connected to the user. ```typescript theme={null} declare function listOAuthProviders(): Promise; ``` React hook to get all OAuth providers connected to the user. ```typescript theme={null} declare function useOAuthProviders(): OAuthProvider[]; ``` ## Passkeys & Sessions Registers a passkey for the user for passwordless authentication. The hostname to associate with the passkey. ```typescript theme={null} declare function registerPasskey(options?: { hostname?: string; }): Promise>; ``` Gets all active sessions for the user. ```typescript theme={null} declare function getActiveSessions(): Promise; ``` Revokes a specific session for the user. The ID of the session to revoke. ```typescript theme={null} declare function revokeSession(sessionId: string): Promise; ``` ## API Keys Creates a new API key. API key creation options. Purpose explanation for the key. Expiration date, or `null` for no expiration. Defaults to `false`. Secret keys are scanned for exposure. ```typescript theme={null} declare function createApiKey(options: { description: string; expiresAt: Date | null; isPublic?: boolean; }): Promise; ``` ```typescript theme={null} const apiKey = await user.createApiKey({ description: "CI token", expiresAt: null, }); console.log(apiKey); ``` Lists the user's API keys. ```typescript theme={null} declare function listApiKeys(): Promise; ``` React hook version of `listApiKeys`. ```typescript theme={null} declare function useApiKeys(): UserApiKey[]; ``` ## Billing & Payments Generates a Stripe checkout URL for purchasing a product. The ID of the product to purchase. ```typescript theme={null} declare function createCheckoutUrl(options: { productId: string; }): Promise; ``` ```typescript theme={null} const checkoutUrl = await user.createCheckoutUrl({ productId: "prod_premium_monthly", }); window.location.href = checkoutUrl; ``` Retrieves item details such as credits or subscriptions. The ID of the item to retrieve. ```typescript theme={null} declare function getItem(itemId: string): Promise; ``` React hook version of `getItem`. The ID of the item to retrieve. ```typescript theme={null} declare function useItem(itemId: string): Item; ``` Creates a Stripe setup intent for adding a payment method. ```typescript theme={null} declare function createPaymentMethodSetupIntent(): Promise; ``` Sets the default payment method from a completed setup intent. The ID of the completed setup intent. ```typescript theme={null} declare function setDefaultPaymentMethodFromSetupIntent( setupIntentId: string, ): Promise; ``` Switches a subscription from one product to another. Subscription switch options. The product ID to switch from. The product ID to switch to. A specific price ID to use. The quantity for the new subscription. ```typescript theme={null} declare function switchSubscription(options: { fromProductId: string; toProductId: string; priceId?: string; quantity?: number; }): Promise; ``` Returns the billing information for the user. ```typescript theme={null} declare function getBilling(): Promise; ``` React hook that returns the billing information. ```typescript theme={null} declare function useBilling(): CustomerBilling; ``` Returns all products available to the user. ```typescript theme={null} declare function listProducts(options?: CustomerProductsListOptions): Promise; ``` React hook that returns all products. ```typescript theme={null} declare function useProducts(options?: CustomerProductsListOptions): CustomerProductsList; ``` Returns all invoices for the user. ```typescript theme={null} declare function listInvoices(options?: CustomerInvoicesListOptions): Promise; ``` React hook that returns all invoices. ```typescript theme={null} declare function useInvoices(options?: CustomerInvoicesListOptions): CustomerInvoicesList; ``` ## Deprecated Whether email-based authentication is enabled for this user. **Deprecated:** Use contact channel's `usedForAuth` instead. `typescript declare const emailAuthEnabled: boolean; ` The OAuth providers connected to this user account. **Deprecated:** Use `getConnectedAccount()` instead. `typescript declare const oauthProviders: readonly { id: string }[]; ` Sends a verification email to the user's primary email address. **Deprecated:** Use contact channel's `sendVerificationEmail` instead. ```typescript theme={null} declare function sendVerificationEmail(): Promise; ``` Returns a JSON object with `accessToken` for non-HTTP protocols. **Deprecated:** Use `getAccessToken()` and `getRefreshToken()` instead. ```typescript theme={null} declare function getAuthJson(): Promise<{ accessToken: string | null }>; ``` React hook that returns the auth JSON. **Deprecated:** Use `useAccessToken()` and `useRefreshToken()` instead. ```typescript theme={null} declare function useAuthJson(): { accessToken: string | null; refreshToken: string | null }; ``` Retrieves a specific connected account. **Deprecated:** Use `getOrLinkConnectedAccount` for redirect behavior, or `getConnectedAccount(\{ provider, providerAccountId \})` for existence check. Connected account identifier. The provider identifier. The provider account identifier. ```typescript theme={null} declare function getConnectedAccount(account: { provider: string; providerAccountId: string; }): Promise; ``` React hook version of `getConnectedAccount`. **Deprecated:** Use `useOrLinkConnectedAccount` for redirect behavior, or `useConnectedAccount(\{ provider, providerAccountId \})` for existence check. Connected account identifier. The provider identifier. The provider account identifier. ```typescript theme={null} declare function useConnectedAccount(account: { provider: string; providerAccountId: string; }): OAuthConnection | null; ``` *** # `ServerUser` Accessible via `hexclaveServerApp.getUser()` on the server. `ServerUser` inherits most `CurrentUser` functionality minus session-dependent methods like `getAuthJson()` and `signOut()`. ## Table of Contents ## Additional Properties Last user activity timestamp. `typescript declare const lastActiveAt: Date; ` Server-only accessible metadata. `typescript declare const serverMetadata: Json; ` Whether the user has been restricted by an administrator. `typescript declare const restrictedByAdmin: boolean; ` The public reason why the user was restricted by an admin, or `null` if not restricted. `typescript declare const restrictedByAdminReason: string | null; ` Private details about the admin restriction (only visible server-side), or `null` if not restricted. `typescript declare const restrictedByAdminPrivateDetails: string | null; ` The country code of the user based on their IP address, or `null` if unknown. `typescript declare const countryCode: string | null; ` Risk scores computed during sign-up, useful for fraud detection. ```typescript theme={null} declare const riskScores: { readonly signUp: { readonly bot: number; readonly freeTrialAbuse: number; }; }; ``` ## Additional Methods Extended update supporting server-only fields. The fields to update on the user. Display name. Set the primary email. Set email verification status. Enable or disable email auth. Set a new password. Client metadata. Client read-only metadata. Server-only metadata. Selected team ID. Profile image URL. TOTP multi-factor authentication secret. Whether OTP authentication is enabled. Whether passkey authentication is enabled. ```typescript theme={null} declare function update(data: ServerUserUpdateOptions): Promise; ``` ```typescript theme={null} await serverUser.update({ primaryEmail: "new@example.com", primaryEmailVerified: true, }); ``` Sets the primary email for the user (server-side only). The new primary email, or `null` to clear it. Whether to mark the email as verified. ```typescript theme={null} declare function setPrimaryEmail( email: string | null, options?: { verified?: boolean }, ): Promise; ``` ```typescript theme={null} await serverUser.setPrimaryEmail("new@example.com", { verified: true }); ``` Sets the server metadata for the user. The new server metadata value. ```typescript theme={null} declare function setServerMetadata(metadata: any): Promise; ``` ```typescript theme={null} await serverUser.setServerMetadata({ plan: "premium", internalId: "abc123" }); ``` Sets the client read-only metadata that clients can read but not write. The new client read-only metadata value. ```typescript theme={null} declare function setClientReadOnlyMetadata(metadata: any): Promise; ``` ```typescript theme={null} await serverUser.setClientReadOnlyMetadata({ role: "editor", tier: "pro" }); ``` Server-side permission assignment. The team scope. The permission to grant. ```typescript theme={null} declare function grantPermission(scope: Team, permissionId: string): Promise; ``` ```typescript theme={null} const team = await serverUser.getTeam("team_id_123"); if (team) { await serverUser.grantPermission(team, "my_permission_id"); } ``` Server-side permission removal. The team scope. The permission to revoke. ```typescript theme={null} declare function revokePermission(scope: Team, permissionId: string): Promise; ``` ```typescript theme={null} const team = await serverUser.getTeam("team_id_123"); if (team) { await serverUser.revokePermission(team, "my_permission_id"); } ``` Returns server-specific contact channel details as `ServerContactChannel[]`. ```typescript theme={null} declare function listContactChannels(): Promise; ``` Lists the teams the user belongs to with cursor-based pagination, optional filtering, and ordering. The returned array carries an extra `nextCursor` property; pass it back as `cursor` to load the next page. For unpaginated access (returning all matching teams), use `listTeams()`. Cursor returned as `nextCursor` from a previous response. Maximum number of items to return. If omitted, all matching teams are returned. The field to sort results by. Whether to sort in descending order. Defaults to `false`. Free-text search on the team's display name (and team ID if the query is a UUID). ```typescript theme={null} declare function listTeamsPaginated(options?: { cursor?: string; limit?: number; orderBy?: "createdAt"; desc?: boolean; query?: string; }): Promise<{ items: ServerTeam[]; nextCursor: string | null }>; ``` ```typescript theme={null} const teams = await serverUser.listTeamsPaginated({ limit: 20 }); if (teams.nextCursor) { const next = await serverUser.listTeamsPaginated({ cursor: teams.nextCursor, limit: 20, }); } ``` React hook version of `listTeamsPaginated`. This should be used on the server-side only. ```typescript theme={null} declare function useTeamsPaginated(options?: { cursor?: string; limit?: number; orderBy?: "createdAt"; desc?: boolean; query?: string; }): { items: ServerTeam[]; nextCursor: string | null }; ``` Creates a new session for the user. Can be used for impersonation. How long the session should last, in milliseconds. Whether this is an impersonation session. ```typescript theme={null} declare function createSession(options?: { expiresInMillis?: number; isImpersonation?: boolean; }): Promise<{ getTokens(): Promise<{ accessToken: string | null; refreshToken: string | null; }>; }>; ``` ```typescript theme={null} const session = await serverUser.createSession({ expiresInMillis: 60 * 60 * 1000, }); const tokens = await session.getTokens(); ``` Grants a product to the user. The product to grant. Either a `productId` or an inline product definition. The ID of an existing product to grant. The quantity to grant. ```typescript theme={null} declare function grantProduct( product: { productId: string; quantity?: number } | { product: InlineProduct; quantity?: number }, ): Promise; ``` ```typescript theme={null} await serverUser.grantProduct({ productId: "prod_premium", quantity: 1, }); ``` *** # `CurrentServerUser` Combines all properties and methods from both `CurrentUser` and `ServerUser`, providing complete user access on the server side. ## Table of Contents React hook variants prefixed with `use` are available in React-like platforms and return unwrapped data rather than promises.