API Reference
Condensed reference for the TryMellon JavaScript SDK public API.
TryMellon
Main entry point. Create a client with your app configuration. Prefer TryMellon.create() so invalid config returns a Result instead of throwing.
import { TryMellon } from '@trymellon/js';
const clientResult = TryMellon.create({
appId: 'YOUR_APP_ID',
publishableKey: 'cli_xxxx',
});
if (!clientResult.ok) throw clientResult.error;
const client = clientResult.value;
TryMellonConfig
| Name | Type | Description |
|---|---|---|
appId | string (required) | Application ID from the TryMellon dashboard. |
publishableKey | string (required) | Client ID (safe for the browser). Starts with cli_. |
apiBaseUrl | string | Override the API base URL. Default: 'https://api.trymellonauth.com'. |
timeoutMs | number | HTTP request timeout (ms). Range 1000–300000. Default: 30000. |
maxRetries | number | Retries for transient errors. Range 0–10. Default: 3. |
retryDelayMs | number | Delay between retries (ms). Range 100–10000. Default: 1000. |
logger | Logger | Custom logger. Default: console. |
sandbox | boolean | If true, signUp/signIn return immediately with a fixed token. See Sandbox mode. |
sandboxToken | string | Custom token in sandbox mode. Default: the exported SANDBOX_SESSION_TOKEN constant. |
enableTelemetry | boolean | Opt-in anonymous event tracking. Default: false. |
telemetrySender | TelemetrySender | Custom telemetry sender. Overrides the default HTTP sender when enableTelemetry is true. |
telemetryEndpoint | string | Custom endpoint for the default telemetry sender. |
origin | string | Override the Origin header sent with API requests. Useful in SSR or non-browser environments where window.location.origin is unavailable. |
contextHashStorage | { getItem, setItem } | Custom storage for the context hash. Defaults to sessionStorage. Override in environments where sessionStorage is unavailable (e.g. React Native, SSR). |
preset | 'saas' | 'web3' | Opt-in surface gate. Default 'saas' exposes the core passkey API. 'web3' additionally exposes client.identity.* and client.siwe.* (F1). Any other value returns INVALID_ARGUMENT. Types narrow based on the preset — 'saas' hides the web3 namespaces at compile time. |
customClaims | Record<string, string | number | boolean> | Opt-in custom claims injected into the session JWT under the namespace https://trymellon.dev/claims. Max 10 keys / 2 KB; each key must match the custom_claims_schema configured on the application. |
Static methods
| Method | Returns | Description |
|---|---|---|
TryMellon.create(config) | Result<TryMellon, TryMellonError> | Factory — validates config and returns a client. Preferred over the constructor. |
TryMellon.isSupported() | boolean | true if the browser supports WebAuthn. |
Deprecated:
new TryMellon(config)— throws on invalid config instead of returning aResult. UseTryMellon.create(config)instead.
signUp(options?)
Registers a new passkey for the user. Returns a Result with sessionToken on success.
const result = await client.signUp({ externalUserId: 'user_123' });
RegisterOptions
| Name | Type | Description |
|---|---|---|
externalUserId | string | Your external user identifier. Optional for anonymous registration; backend returns external_user_id in init/validation. |
authenticatorType | 'platform' | 'cross-platform' | Authenticator preference. |
successUrl | string | Redirect URL after success (must be in allowlist). |
signal | AbortSignal | Cancel the operation. |
external_user_id | string | Deprecated. Use externalUserId instead. |
RegisterResult
| Name | Type | Description |
|---|---|---|
success | true | Always true on success. |
credentialId | string | The new passkey credential ID. |
status | string | Always 'registered' on success. |
sessionToken | string | Session token — send to your backend. |
user | object | { userId, externalUserId?, email?, metadata? } |
redirectUrl | string? | Set when successUrl was passed and allowed. |
signIn(options?)
Authenticates the user with an existing passkey. Returns a Result with sessionToken on success.
const result = await client.signIn({ externalUserId: 'user_123' });
AuthenticateOptions
| Name | Type | Description |
|---|---|---|
externalUserId | string | Your external user identifier. |
hint | string | Credential hint for the browser. |
successUrl | string | Redirect URL after success. |
signal | AbortSignal | Cancel the operation. |
mediation | 'optional' | 'conditional' | 'required' | WebAuthn mediation preference. |
external_user_id | string | Deprecated. Use externalUserId instead. |
AuthenticateResult
| Name | Type | Description |
|---|---|---|
authenticated | boolean | true on success. |
sessionToken | string | Session token — send to your backend. |
user | object | { userId, externalUserId?, email?, metadata? } |
signals | object? | { userVerification?, backupEligible?, backupStatus? } |
redirectUrl | string? | Set when successUrl was passed and allowed. |
session.verify(sessionToken)
Client-side check: validates a session token against the API. Returns a Result indicating whether the session is valid. You must pass the token; the SDK does not read cookies. See Session validation.
const result = await client.session.verify(sessionToken);
if (result.ok && result.value.valid) {
// Session is valid
}
SessionValidateResponse
Fields are camelCase in the SDK response. The raw REST API returns snake_case fields nested in a data envelope — see Backend validation for the raw HTTP contract.
| Name | Type | Description |
|---|---|---|
valid | boolean | Whether the session is valid. |
userId | string | TryMellon user ID. |
externalUserId | string | Your external user ID. |
tenantId | string | Tenant ID. |
appId | string | Application ID. |
session.verifyOffline(sessionToken)
Offline JWT validation — verifies the RS256 signature and claims locally against the JWKS exposed at /.well-known/jwks.json. No network round-trip per call; the JWKS is cached for 1 hour. Available since SDK v3.4.0. See Backend validation for when to prefer offline vs remote.
const result = await client.session.verifyOffline(sessionToken);
if (result.ok) {
console.log(result.value.userId, result.value.customClaims);
}
Runs on WebCrypto. RS256 is locked — any other algorithm is rejected. Clock skew tolerance is ±30 s. The namespace https://trymellon.dev/claims is flattened into the top-level customClaims field.
SessionClaims
| Name | Type | Description |
|---|---|---|
userId | string | TryMellon user ID. |
externalUserId | string? | Your external user ID. |
tenantId | string | Tenant ID. |
appId | string | Application ID. |
iat / exp | number | Issued-at / expires-at (Unix seconds). |
kid | string | RFC 7638 thumbprint of the signing key. |
customClaims | Record<string, unknown>? | Flattened from https://trymellon.dev/claims when present. |
capabilities()
Returns the client’s WebAuthn environment status. Use to decide whether to offer passkey or email fallback.
const status = await client.capabilities();
ClientStatus
| Name | Type | Description |
|---|---|---|
isPasskeySupported | boolean | Whether the browser supports WebAuthn. |
platformAuthenticatorAvailable | boolean | Whether a platform authenticator (e.g. Touch ID) is available. |
recommendedFlow | 'passkey' | 'fallback' | Recommended auth flow based on environment. |
on(event, callback)
Subscribe to SDK events (e.g. for loading spinners or analytics). Returns an unsubscribe function.
const unsub = client.on('success', (payload) => {
console.log(payload.operation, payload.token);
});
// later: unsub();
Events: start, success, error, cancelled. See Events & Error handling.
otp
Email OTP fallback when WebAuthn is not available.
| Method | Returns | Description |
|---|---|---|
otp.send({ userId, email }) | Promise<Result<void>> | Sends OTP to the given email. userId is your external user id. |
otp.verify({ userId, code, successUrl? }) | Promise<Result<{ sessionToken, redirectUrl? }>> | Verifies the code and returns a session token. |
See Fallback by email.
identity
Link and unlink secondary identifiers (email, wallet) on a signed-in user. Available only when preset: 'web3' — on a 'saas' client the namespace is absent at compile time. userId is implicit: resolved from the current session. Calling any method without an active session returns INVALID_ARGUMENT.
| Method | Returns | Description |
|---|---|---|
identity.linkEmail({ email }) | Promise<Result<{ challengeId }>> | Starts an email-link challenge. Sends a one-time code to email; finalize with verifyEmailLink. |
identity.verifyEmailLink({ challengeId, code }) | Promise<Result<{ identifierId }>> | Completes the link. Emits identifier.linked webhook. |
identity.list() | Promise<Result<{ identifiers: Identifier[] }>> | Lists all identifiers bound to the current user (type: 'email' | 'wallet' | 'custom'). |
identity.unlink({ identifierId }) | Promise<Result<void>> | Removes an identifier. Emits identifier.unlinked webhook. |
See Identity linking.
siwe
Sign-In with Ethereum (EIP-4361). Available only when preset: 'web3'. prepareMessage is a pure helper — zero dependencies, safe to call outside a session. verifyAndSignIn issues a session token after signature verification.
| Method | Returns | Description |
|---|---|---|
siwe.getNonce({ address, chainId }) | Promise<Result<{ nonce, expiresAt }>> | Fetches a server-issued nonce. chainId must be in the application’s allowed_chain_ids allowlist. |
siwe.prepareMessage(params) | string | Builds an EIP-4361 message from { domain, address, statement?, uri, version, chainId, nonce, issuedAt, ... }. Pure function — no network. Also exported standalone as prepareSiweMessage from @trymellon/js/web3. |
siwe.verifyAndSignIn({ message, signature }) | Promise<Result<{ sessionToken, user }>> | Verifies the signature, finds-or-creates the user bound to the wallet address, and returns a session token. |
See SIWE login.
crossDevice
Cross-device authentication (QR login). Desktop initiates, mobile approves.
| Method | Returns | Description |
|---|---|---|
crossDevice.start() | Promise<Result<CrossDeviceInitResult>> | Creates a cross-device session. Returns { session_id, qr_url, expires_at, polling_token }. |
crossDevice.startRegistration(options?) | Promise<Result<...>> | Creates a cross-device registration session. options.externalUserId is optional — omit for anonymous registration. |
crossDevice.waitForCompletion(sessionId, signal?, pollingToken?) | Promise<Result<...>> | Blocks until the mobile device approves. Returns { status, sessionToken, userId, redirectUrl? }. sessionToken and userId are only present when status === 'completed' — which is the only case this resolves successfully (timeout/abort return an error). |
crossDevice.getContext(sessionId) | Promise<Result<CrossDeviceContextResult>> | Gets session context (used on the mobile side). |
crossDevice.approve(sessionId) | Promise<Result<...>> | Approves the cross-device session from the mobile device. |
See Cross-device auth.
enroll(options)
Enrolls a device or entity using a single-use ticket issued by your backend. Used for the Entity Enrollment (Keys & Padlock) flow.
const result = await client.enroll({ ticketId: 'ticket_xxx' });
if (result.ok) {
console.log('Session token:', result.value.sessionToken);
console.log('Credential:', result.value.credentialId);
}
| Name | Type | Description |
|---|---|---|
ticketId | string (required) | Enrollment ticket ID issued by your backend via POST /v1/enrollment/tickets. |
signal | AbortSignal | Cancel the operation. |
EnrollmentResult
| Name | Type | Description |
|---|---|---|
sessionToken | string | Session token — send to your backend. |
credentialId | string | The new passkey credential ID. |
userId | string | TryMellon user ID for the enrolled entity. |
entityId | string? | Entity ID bound to the credential (when issued with one). |
Returns Result<EnrollmentResult, TryMellonError>. See Entity Enrollment.
getContextHash()
Returns the context hash bound to the current browser session. This hash ties an enrollment ticket to a specific client context — pass it when issuing a ticket from your backend so replay on a different origin fails.
const contextHash = client.getContextHash();
// Send to your backend when requesting a ticket
Returns string (64-char hex, SHA-256). Persisted in sessionStorage for the duration of the session.
bridge
Bridge flows for QR-based enrollment and authentication from a second device (e.g. mobile scanning a desktop QR).
| Method | Returns | Description |
|---|---|---|
bridge.getContext(sessionId, kind) | Promise<Result<BridgeContextResponse>> | Gets the bridge session context. kind: 'enrollment' or 'auth'. |
bridge.verifyPresence(sessionId, pin, kind) | Promise<Result<BridgeChallengeResponse>> | Verifies the presence PIN and returns WebAuthn options. |
bridge.complete(sessionId, options?) | Promise<Result<BridgeResult>> | Completes the ceremony. options.kind ('enrollment' or 'auth') is required. For enrollment: options.ticketId (ticket ID) and options.entityId are also required. Optionally pass options.presencePin or options.onPinRequired if PIN was not already verified. |
bridge.waitForResult(sessionId, options?) | Promise<Result<BridgeStatusSnapshot>> | Polls or listens via SSE until the bridge session reaches a terminal state. |
See Cross-device auth and Entity Enrollment.
passkey.recover(options)
Recovers an account using an email OTP and creates a new passkey. Requires server-side initiation first.
const result = await client.passkey.recover({
externalUserId: 'user_123',
otp: '632145',
});
| Name | Type | Description |
|---|---|---|
externalUserId | string (required) | The external user ID. |
otp | string (required) | The 6-digit OTP from the recovery email. |
Returns Result<RecoverAccountResult, TryMellonError>. See Account Recovery.
Wire format note: The SDK sends
external_id(notexternal_user_id) to the recovery endpoint — a historical field preserved for backend compatibility. PassexternalUserIdin your SDK call as usual; the conversion is automatic.
version()
Returns the SDK version string.
console.log(client.version()); // e.g. '3.0.0'
SANDBOX_SESSION_TOKEN
Exported constant — the fixed token returned in sandbox mode. Use it in your backend to identify sandbox sessions during development.
import { SANDBOX_SESSION_TOKEN } from '@trymellon/js';
Utility exports
Helper functions exported from @trymellon/js:
| Export | Description |
|---|---|
isTryMellonError(e) | Type guard — returns true if e is a TryMellonError. |
ConsoleLogger | Default logger implementation. Pass a custom object matching the Logger interface to suppress or redirect SDK logs. |
resolveCredentialName(aaguid) | Resolves a human-readable authenticator name from an AAGUID (e.g. 'Touch ID', 'YubiKey 5'). |
getDeviceName() | Returns a human-readable name for the current device/authenticator context. |
@trymellon/js/platform · hosted onboarding
Sub-path for platforms that onboard their own maintainers onto TryMellon. Stateless — no publishable key. Full guide → Hosted onboarding.
import { createPlatform } from '@trymellon/js/platform';
const platform = createPlatform({ apiBaseUrl: 'https://api.trymellonauth.com' });
createPlatform(config?)
| Arg | Type | Description |
|---|---|---|
config.apiBaseUrl | string? | Override the API base. Defaults to https://api.trymellon.com. |
Returns TryMellonPlatform — 3 methods.
createSignupLink(options)
const link = await platform.createSignupLink({
returnUrl: 'https://acme.com/onboarded',
userRole: 'maintainer',
refreshUrl: 'https://acme.com/signup-expired', // optional
prefill: { companyName: 'ACME', email: 'f@acme.com' }, // UX only
});
// link.ok = true ⇒ link.value = { sessionId, hostedUrl, expiresInSeconds }
Client-side guards (fail-fast, zero HTTP): returnUrl must be https, refreshUrl (if present) must also be https.
getSignupStatus(sessionId)
Returns { status: 'pending_data' | 'pending_passkey' | 'completed' | 'expired' | 'failed', hostedUrl?, expiresInSeconds? }.
awaitSignupCompletion(sessionId, options?)
const controller = new AbortController();
const done = await platform.awaitSignupCompletion(sessionId, {
signal: controller.signal,
intervalMs: 2_000, // default
maxAttempts: 60, // default (~2 min)
});
Polls getSignupStatus until terminal. Rejects ABORT_ERROR on signal.abort(), TIMEOUT on exhaustion, SESSION_EXPIRED/SERVER_ERROR on terminal failure.
Type-level narrow: on the main TryMellon client, client.platform is typed never across every preset. Use the sub-path import — the TS compiler will reject client.platform.*.
Web Components
The SDK ships pre-built Web Components in @trymellon/js/ui. See Web Components for full documentation.
<script type="module">
import '@trymellon/js/ui';
</script>
<trymellon-auth app-id="YOUR_APP_ID" publishable-key="cli_xxxx"></trymellon-auth>
Result type
All async methods that can fail return a Result<T, E>:
| Name | Type | Description |
|---|---|---|
result.ok | boolean | true if success, false if error. |
result.value | T | The success value when result.ok is true. |
result.error | E | The error when result.ok is false. |
Check result.ok before accessing result.value; use !result.ok and result.error for error handling.
TryMellonError
| Name | Type | Description |
|---|---|---|
code | TryMellonErrorCode | Error code for programmatic handling. |
message | string | Human-readable error message. |
details | unknown? | Additional error context. |
isTryMellonError | true | Always true — use for type narrowing. |
Error codes: NOT_SUPPORTED, USER_CANCELLED, PASSKEY_NOT_FOUND, SESSION_EXPIRED, NETWORK_FAILURE, INVALID_ARGUMENT, TIMEOUT, ABORT_ERROR, CHALLENGE_MISMATCH, RATE_LIMIT_EXCEEDED, TICKET_NOT_FOUND, TICKET_EXPIRED, TICKET_ALREADY_USED, PIN_MISMATCH, PIN_LOCKED, BRIDGE_SESSION_EXPIRED, OTP_INVALID_OR_EXPIRED, ACTION_CHALLENGE_EXPIRED, ACTION_ALREADY_CLAIMED, ACTION_PAYLOAD_MISMATCH, SECRET_ROTATION_FORBIDDEN, JWT_KID_MISMATCH, INTROSPECTION_FAILED, CUSTOM_CLAIM_NOT_ALLOWED, CUSTOM_CLAIMS_TOO_LARGE, FORBIDDEN, SERVER_ERROR, UNKNOWN_ERROR. See Events & Error handling.