Skip to main content
Every SDK hook surfaces errors as ApiError. Each ApiError carries three fields:
  • code — the API’s stable machine-readable identifier (e.g. refresh_invalid). May be undefined if the response body was missing or malformed.
  • status — the HTTP status code (e.g. 401, 404).
  • message — human-readable, may reword.
Branch on code for recovery logic; show message in toasts / banners; use status for catch-all 4xx-vs-5xx checks.

Import

import { ApiError } from "@calm-xyz/react";

Usage

app/page.tsx
"use client";
import { ApiError, useCalm, useSession } from "@calm-xyz/react";

function SessionStatus() {
  const { address } = useCalm();
  const { error } = useSession({ address });

  if (error?.code === "refresh_invalid") {
    // The refresh cookie is gone or stale — sign the user out at the
    // wallet/identity layer and remount the provider.
    return <button onClick={signOut}>Sign back in</button>;
  }

  if (error) {
    return <p role="alert">{error.message}</p>;
  }

  return null;
}

Error codes

The codes below are stable across SDK minor versions. Catch-all on status for codes not listed (rare; the API tries to emit a stable code on every 4xx).

Session

codestatuswhen
refresh_invalid401The browser’s refresh cookie is missing, expired, or wallet-mismatched. Sign out at your wallet/identity layer and remount the provider.
invalid_token401The Privy / Dynamic identity token failed verification (bad signature, wrong audience, expired).
token_required401The session-create request arrived without an Authorization header.
origin_not_allowed403Request Origin isn’t in the credential’s allowlist. Check your Calm dashboard.
wallet_not_linked403The wallet on the session request isn’t among the Privy/Dynamic user’s linked accounts. Often the identity-token toggle is off, so the wallet didn’t ride inside the JWT.
publishable_key_missing400No publishable key reached the API — usually a bundler stripping the env var. Check NEXT_PUBLIC_CALM_KEY.
invalid_publishable_key400 / 401calmKey is malformed (400) or unknown / revoked (401). Check your Calm dashboard.
binding_expired401The SIWE nonce expired (5-minute TTL) before the user signed. Re-mount or retry.
siwe_invalid401The signed SIWE message failed verification — wrong nonce, wrong recovered address, time bounds, or unparseable input.

Wallet

codestatuswhen
session_required401No calm_session cookie on a cookie-credentialed read. The SDK gates these reads on the session being live, so seeing this in a hook’s error field usually means the session was cleared mid-render or the provider mounted without a wallet address.
session_invalid401The session cookie was present but failed verification.
invalid_wallet400The :address path parameter isn’t a valid Ethereum address.
validation_error400Body parsed as JSON but failed zod validation. message carries the field-level details.

Retry behaviour

The SDK’s hooks retry 5xx and network errors up to 3 times with react-query’s default backoff. 4xx is treated as terminal — codes like invalid_wallet or validation_error describe a fixed state, not a transient failure, so retrying just spams the console and the server.