Additional info
Error handling
Unified error shape, error codes, and HTTP mapping guidelines.
Error shape
Public APIs return a discriminated union-like Result<T>:
type Result<T> = { result?: T; error?: UsefulKeyError };
type UsefulKeyError = {
code: string;
message: string;
retryable?: boolean;
cause?: unknown;
meta?: Record<string, unknown>;
};Prefer handling error explicitly rather than relying on exceptions.
Normalization and metadata
Errors are normalized via a utility so callers always receive a stable shape:
toError(err: unknown, fallbackCode: string, meta?: Record<string, unknown>): UsefulKeyError- Preserves any thrown object that already has string
codeandmessage. - Otherwise uses
fallbackCodeand derivesmessagefrom the thrown value. - Always attaches the original value on
causeand merges providedmeta. - Core paths include
meta.opto indicate the operation (e.g."getKey","verifyKey","createKey","revokeKey","extendKeyExpiry","hardRemoveKey","sweepExpired").
Core error codes
These codes originate from core and adapters:
UNKNOWNKEYSTORE_READ_FAILEDKEYSTORE_WRITE_FAILEDKEYSTORE_REVOKE_FAILEDKEYSTORE_SWEEP_UNSUPPORTEDANALYTICS_TRACK_FAILEDKEY_GENERATION_FAILEDPLUGIN_BLOCKEDPLUGIN_SETUP_FAILED
Additionally, some operations may emit validation-style codes when inputs are invalid (for example, INVALID_INPUT from extendKeyExpiry). Plugins may emit their own codes as well (for example, KEY_NOT_FOUND, INVALID_ARGUMENT).
Verification reasons
When verifyKey rejects, the result is present with valid: false and a reason:
not_foundrevokedexpiredusage_exceededblocked_by_plugin(default when a plugin rejects without a custom reason)insufficient_scoperate_limited(configurable in the plugin)namespace_required(when rate limit plugin is enabled andnamespaceis missing)
HTTP mapping guidelines
valid: true→ 200 OKvalid: falsereasons:not_found→ 401/404 (pick one; 401 prevents leaking existence)revoked,expired→ 401 Unauthorizedusage_exceeded→ 402/403/429 depending on product semantics; commonly 429insufficient_scope→ 403 Forbiddenrate_limited→ 429 Too Many Requests (include reset info if available)namespace_required→ 400 Bad Request
Errors from core/adapters (error.code):
KEYSTORE_*→ 500 Internal Server Error (with retryable depending on store)KEY_GENERATION_FAILED→ 500PLUGIN_BLOCKED→ translate to a 4xx aligned with the plugin’sreason
Example translation (Express)
const res = await uk.verifyKey({ key, identifier: req.ip, namespace: "api" });
if (res.error) return reply.status(500).json(res.error);
if (!res.result!.valid) {
const r = res.result!;
const status = r.reason === "rate_limited" ? 429
: r.reason === "insufficient_scope" ? 403
: 401;
return reply.status(status).json(r);
}
return reply.json(res.result);