Typed errors
Every Enlitt failure is a typed, non-retryable refusal. Half are safe to show a user; half must stay in your logs.
Every Enlitt-originated failure — standard or premium — extends one base
class, EnlittError, and carries a stable code. Admission refusals arrive
as typed, non-retryable errors, not as a call outcome your code has to
inspect a result shape to notice: catch them like any other thrown error.
readonly providerInvoked: boolean; // always false on this hierarchy
readonly retryable: boolean; // false for every refusal except rate limiting
readonly code: string;
readonly retryAfterMs?: number;
readonly requestId?: string;
readonly authorizeUrl?: string; // only on the four credit/limit errors belowproviderInvoked is always false on every instance of this hierarchy:
Enlitt refuses before a provider is ever reached, so this hierarchy never
represents a provider's own failure. A provider's own error (a bad
completion, a provider-side 500) is rethrown unchanged, with its original
type — it is never wrapped as an EnlittError.
The audience split
This is the one distinction to get right before you show any Enlitt error to anyone: some codes are the end user's business, and some are yours alone.
Safe to render directly to the end user
| Class | Code | What it means |
|---|---|---|
EnlittLoginRequiredError | personal_login_required | No usable personal session. Send the user through login or reauthorization. |
EnlittLimitExceededError | premium_per_call_limit_exceeded | This call exceeds the user's own per-call premium ceiling. See Premium limits. |
EnlittInsufficientCreditsError | premium_balance_exhausted, standard_balance_exhausted | The user's premium ceiling, or their plan's standard ceiling, is reached for the period. |
EnlittSubscriptionInactiveError | subscription_inactive | No subscription period covers now. Every registered user holds at least the free plan, so this is exceptional — treat it as a defect signal, not an ordinary funnel step, if you see it at any volume. |
None of these four carry the user's actual balance, remaining capacity,
plan, or period — that is the user's own private financial position. The
first three may carry authorizeUrl, a navigation target to Enlitt's own
screen where the user can act and see the real numbers; your UI links to it,
it never mirrors the figures behind it.
Business logs only — never render to an end user
| Class | Code | What it means |
|---|---|---|
EnlittConfigurationError | configuration_invalid | Missing/invalid secret, browser use, bad base URL, bad call options, missing session context. Thrown locally, before any network call. |
EnlittAuthenticationError | invalid_api_key | The presented business key is missing, invalid, or revoked. |
EnlittAuthorizationError | insufficient_permissions | The presented business key is valid but used outside its authenticated application or scope. |
These three describe a problem with your integration, not the user's account or the request they made. Showing one to an end user leaks nothing financial, but it is meaningless and alarming to them — it belongs in your own error tracking.
Retryable, and everything else
EnlittRateLimitError (rate_limit_exceeded) is the only retryable
refusal, and the only class that carries retryAfterMs in practice. It is
deliberately not an EnlittInsufficientCreditsError: conflating a transient
per-minute wait with an exhausted balance would tell a user to buy credits
they do not need.
Every other wire code with no distinct business action of its own —
conflict_error, operation_in_progress, idempotency_replay_unavailable,
invalid_value, unsupported_parameter, unsupported_value,
reauthentication_required, not_found, request_too_large,
server_error, provider_error, no_capable_route — surfaces as the base
EnlittError class directly, carrying that code. A wire code newer than
your installed SDK version also degrades to this base class rather than
throwing an unrecognized-type error, so an older SDK build keeps working
against a backend that has since added one more code.
Checking a code
import { EnlittError, EnlittInsufficientCreditsError } from "@enlitt/sdk/server";
catch (error) {
if (error instanceof EnlittInsufficientCreditsError) {
// error.code is "premium_balance_exhausted" or "standard_balance_exhausted"
} else if (error instanceof EnlittError) {
// any other Enlitt refusal — check error.code before deciding what, if
// anything, to show the user
} else {
throw error; // not an Enlitt failure at all — a provider error, or a bug
}
}