The SDK never throws plain Error objects. Every failure is an IntelliAuthError with a stable code field you can branch on. This is the single error type your app needs to handle.
The shape
class IntelliAuthError extends Error {
code: IntelliAuthErrorCode // stable enum, listed below
message: string // human-readable, may change between versions
details?: Record<string, unknown> // extra context, code-specific
cause?: unknown // wrapped underlying error if any
recoverable: boolean // hint about whether a retry makes sense
}
Match on code. Do not match on message — message text is not part of the API and may improve over time without notice.
The codes
The codes group into four families.
Session
Code | When | Recoverable? |
|---|---|---|
| The user's session has expired since the last token refresh. | Yes — call |
| An admin revoked the session, or the user revoked it from another device. | Yes — sign in again. |
| A method that requires a session was called without one. | Yes — sign in. |
| Background session check could not complete. | Sometimes — usually network. |
Auth flow
Code | When |
|---|---|
| The |
| The redirect URI does not match what's registered on the application. |
| The user has not yet granted the requested scopes. |
| The platform demands a second factor. Use |
| The current AAL is too low for the action. Use |
Network / platform
Code | When | Recoverable? |
|---|---|---|
| The fetch itself failed (DNS, TCP, TLS). | Yes — retry with backoff. |
| The user or app hit a rate limit. | Yes — wait, look for |
| The platform returned a 5xx. | Yes — retry; consider a circuit breaker. |
| The requested entity doesn't exist or you can't see it. | No — usually a config issue. |
MFA / credentials
Code | When |
|---|---|
| The WebAuthn ceremony did not complete. |
| The submitted TOTP code was wrong or expired. |
| The SMS delivery did not succeed. |
| The submitted backup code was wrong or already consumed. |
The error code index lists every code in the SDK with the recommended user-facing copy.
Branching pattern
const { loginWithRedirect } = useIntelliAuth()
async function trySignIn() {
try {
await loginWithRedirect()
} catch (e) {
if (!(e instanceof IntelliAuthError)) throw e
switch (e.code) {
case 'mfa_required':
// The SDK already started the MFA flow. Show the MFA step UI.
showMfaPrompt(e.details?.flow_id)
break
case 'state_mismatch':
// Don't retry silently — there may be a real CSRF attempt.
showError('Sign-in security check failed. Please try again from the home page.')
break
case 'rate_limited':
showError(`Too many attempts. Try again in ${e.details?.retry_after_s ?? 60}s.`)
break
default:
showError(e.message)
}
}
}
Global error handler
The provider supports onError. Use it for telemetry — not for user-facing UI:
<IntelliAuthProvider
/* ... */
onError={(err) => {
telemetry.captureException(err, { sdk: 'intelliauth' })
}}
>
Per-call try/catch is still required for user-facing branching; onError is the firehose.
Wrapping errors from other layers
If you want to throw application-level errors that also happen to wrap an IntelliAuthError — say, a "PaymentBlockedError" that fires when MFA is required for a payment — use the cause:
try {
await api.charge({ amount: 100 })
} catch (e) {
if (e instanceof IntelliAuthError && e.code === 'step_up_required') {
throw new PaymentBlockedError('Confirm your identity to continue.', { cause: e })
}
throw e
}
The downstream error handler can still match cause against IntelliAuthError when it needs to drive a step-up.
Logging
IntelliAuthError serialises sensibly via JSON.stringify — code, message, details come out cleanly. The cause chain is not serialised by default; if you log via a structured logger, include cause explicitly.
Never log access tokens or refresh tokens. The SDK does not put them in error details; your code shouldn't either.