The SDK uses typed error classes so you can catch specific failure modes. Every SDK error extends SdkError.
Error class hierarchy
Code
SdkError Base class for all SDK errors├── ApiError Any non-2xx REST response│ ├── InvalidRequest 400 — malformed or rejected request│ ├── InvalidNonce 400 — nonce reused or not increasing│ ├── MissingNonce 400 — nonce not present in payload│ ├── InvalidSignature 400 — HMAC signature mismatch│ ├── MissingRole 403 — API key lacks required role│ ├── AcceptTermsRequired 403 — must accept terms before trading│ ├── NotFoundError 404 — resource does not exist│ ├── InsufficientFunds 406 — not enough balance│ ├── RateLimitError 429 — rate limit exceeded│ └── ServiceUnavailable 5xx — exchange down or errored├── ValidationError Request body fails documented shape check├── ConnectionError WebSocket open failed or dropped├── WebSocketRequestError Non-success WebSocket method response├── RequestTimeoutError Operation exceeded deadline├── RequestAbortedError Caller cancelled via AbortSignal├── OAuthStateError OAuth callback state mismatch├── OAuthAuthorizationError OAuth authorization denied├── OAuthTokenError OAuth token endpoint failure├── EndpointMismatch Internal: payload path mismatch└── ResyncRequiredError Order book gap detected
Catching errors
Catch broadly or narrowly depending on your needs:
Code
import { SdkError, ApiError, RateLimitError, AcceptTermsRequired,} from "gemini-markets/server";try { await client.predictions.placeOrder({ /* ... */ });} catch (err) { if (err instanceof AcceptTermsRequired) { // User must accept terms first await client.predictions.acceptTerms(); // retry... } else if (err instanceof RateLimitError) { // Back off and retry console.log("Rate limited, status:", err.status); } else if (err instanceof ApiError) { // Any other API error console.log(err.status, err.reason, err.code, err.category); } else if (err instanceof SdkError) { // SDK-level error (timeout, connection, validation) console.log(err.message); }}
Error metadata
Every ApiError carries structured fields for programmatic handling:
Code
catch (err) { if (err instanceof ApiError) { err.status; // HTTP status code (400, 403, 429, etc.) err.reason; // Server error string ("InvalidNonce", "RateLimit", etc.) err.code; // Stable SDK code ("invalid_request", "rate_limited", etc.) err.category; // Error family ("validation", "authentication", "rate_limit", etc.) err.serverCode; // Raw server error code, if present err.metadata; // Request metadata (endpoint, method, correlation ID, status) }}
The code and category fields are stable across SDK versions — use them for programmatic branching. The reason field is the verbatim server string and may change.
Retries
The SDK retries automatically for generated safe-read operations only (GET-equivalent endpoints). Retries trigger on:
Use serializeError() to log errors safely. It strips raw response bodies and credentials while preserving structure:
Code
import { serializeError } from "gemini-markets/server";try { await client.trading.createNewOrder({ /* ... */ });} catch (err) { // Safe for logging — no secrets, no raw bodies console.log(JSON.stringify(serializeError(err))); // Include raw body only for debugging (treat as sensitive) console.log(serializeError(err, { includeRawBody: true }));}
import { ConsoleLogger } from "gemini-markets/server";const client = await createClient({ auth, logger: new ConsoleLogger({ minLevel: "debug" }),});
Log levels: debug, info, warn, error. Set minLevel to control verbosity.
What's redacted
Diagnostic events and serialized errors never include: request bodies, response bodies, credentials, signatures, tokens, API keys, or nonces. They do include: endpoint paths, HTTP methods, status codes, correlation IDs, exchange request IDs, rate-limit headers, retry counts, and content types.
Use serializeError(err, { includeRawBody: true }) only when you need the raw body for debugging, and treat that output as sensitive.