Error codes
Every displayable failure in the SDK is reported as one stable ToriiError
object: the sign-in / sign-up callbacks, profile updates, and the data hooks’
error fields all share it. The SDK fills in a sensible message; you branch on
the machine-readable code to drive your own side effects (analytics,
redirects, a custom banner).
The error object
Section titled “The error object”interface ToriiError { code: ToriiErrorCode; message: string; // sensible default text; localize your own copy keyed on code status?: number; // HTTP status, when the failure came from an API response details?: unknown; // raw problem body or original error, for diagnostics}Where you get one
Section titled “Where you get one”| Surface | Prop / field |
|---|---|
<SignIn> / <LoginForm> | onLoginError(error) |
<SignUp> / <SignupForm> | onSignupError(error) |
<UserProfile> / <UserDashboard> | onUpdateError(error) |
<ToriiProvider> | events.onError(error), session-refresh / sign-out failures |
useSessions, useIdentities, useEmailAddresses, useUserProfile, useDataRequest, useDeleteAccount | .error |
updateProfile / requestDeletion | result.error (when !result.ok) |
<SignIn onLoginError={(error) => { if (error.code === 'account_banned') analytics.track('blocked_signin'); // error.message is a sensible default, render it directly if you want. }}/>The one exception: the runtime-load failure on
useRuntimeStatus()stays a rawError. It’s a CDN/loader infrastructure failure, not an API error.
ToriiErrorCode is a closed union, grouped by origin:
| Code | Meaning |
|---|---|
invalid_credentials | Wrong email / password. |
account_banned | The account is banned and cannot sign in. |
email_already_exists | Sign-up, an account with that email already exists. |
weak_password | The chosen password failed the environment’s strength policy. |
invalid_reset_code | The password-reset code is wrong or malformed. |
provider_error | An OAuth provider flow failed (IdP error, denied consent, callback error). |
configuration_error | The environment is misconfigured (e.g. an enabled provider with missing credentials). |
signup_failed | Sign-up, a generic failure with no more specific code. |
invitation_invalid | Invitation sign-up, the invitation token is unknown, malformed, or already consumed. |
invitation_expired | Invitation sign-up, the invitation has expired. |
invitation_account_exists | Invitation sign-up, an account already exists for the invited email (sign in instead). |
captcha_failed | The Turnstile CAPTCHA challenge was rejected. |
captcha_unavailable | The CAPTCHA widget couldn’t load (ad blocker, CSP). |
origin_not_allowed | The browser’s origin isn’t in the environment’s allowed-origins list. Add it under Settings → Allowed origins. |
expired | A one-time link / token (signup-continuation, verification) has expired. |
already_used | A one-time link / token has already been consumed. |
network_error | The request never reached the server (offline, DNS, CORS, connection reset). |
rate_limited | The server returned 429. Back off and retry later. |
unauthorized | The server returned 401, not authenticated (re-authenticate). |
forbidden | The server returned 403, authenticated but not allowed. |
not_found | The server returned 404. |
request_failed | A non-2xx response with no more specific mapping. The default for HTTP failures. |
unknown | No code could be determined (defensive fallback). |
When the server sends a machine-readable code in its problem body, that code
wins (so origin_not_allowed, invalid_reset_code, weak_password, … stay
branchable). Otherwise the code is derived from the HTTP status: a thrown fetch
→ network_error; 401 → unauthorized; 403 → forbidden; 404 →
not_found; 429 → rate_limited; any other non-2xx → request_failed.
Branching exhaustively
Section titled “Branching exhaustively”The union is closed, so you can switch over it without a default and let
TypeScript flag a missed case if a new code is ever added:
import type { ToriiError } from '@torii-js/torii-react';
function handleError(error: ToriiError) { switch (error.code) { case 'invalid_credentials': case 'account_banned': case 'email_already_exists': case 'weak_password': case 'invalid_reset_code': case 'invitation_invalid': case 'invitation_expired': case 'invitation_account_exists': return; // user-fixable, render error.message case 'origin_not_allowed': case 'configuration_error': console.error('[app] Torii config error:', error.message); return; // your setup is wrong, log it loudly in dev case 'network_error': case 'rate_limited': case 'captcha_failed': case 'captcha_unavailable': showRetryToast(); return; // transient, offer a retry case 'provider_error': case 'signup_failed': case 'unauthorized': case 'forbidden': case 'not_found': case 'request_failed': case 'expired': case 'already_used': case 'unknown': reportError(error); return; }}message is a usable default, so for the common case you can render it directly
and only branch on code for behaviour.
TypeScript
Section titled “TypeScript”import type { ToriiError, ToriiErrorCode } from '@torii-js/torii-react';