GeminiGemini
SandboxGet API key
  • Crypto Trading
  • Prediction Markets
  • SDKs & Tools
Changelog
Gemini Crypto Exchange LogoGemini Crypto Exchange Logo

© 2026 Gemini Space Station, Inc.

Overview
TypeScript SDK
    QuickstartAuthenticationWebSocketError HandlingPatterns & Recipes
    API Reference
    Deep Dives
TypeScript SDK

TypeScript SDK — Authentication

The SDK supports three authentication strategies. Pick the one that matches your application:

StrategyImportUse when
HMACgemini-markets/serverServer-side apps with API key + secret
OAuth (confidential)gemini-markets/serverServer-side apps acting on behalf of users
OAuth (public/PKCE)gemini-markets/browserBrowser and mobile apps — no client secret

HMAC authentication

HMAC is the simplest path for server-to-server integration. Every request is signed with your API secret using HMAC-SHA384.

Code
import { createClient, HmacAuth } from "gemini-markets/server"; const client = await createClient({ env: "sandbox", auth: new HmacAuth({ apiKey: process.env.GEMINI_API_KEY, apiSecret: process.env.GEMINI_API_SECRET, }), }); const balances = await client.accountServices.getAvailableBalances({}); client.close();

The SDK handles payload encoding, nonce generation, and signature computation automatically. You never construct headers manually.

Nonce modes

Every authenticated request includes a nonce to prevent replay attacks. The SDK supports two modes:

Code
// Monotonic (default) — strictly increasing counter based on Date.now() new HmacAuth({ apiKey, apiSecret }); // Time-based — Unix epoch seconds. Required by some sandbox configurations. new HmacAuth({ apiKey, apiSecret, nonceMode: "time-based" });

Use "time-based" if the sandbox returns InvalidNonce errors with the default mode.

How HMAC signing works

For reference, this is what the SDK does on every authenticated request:

  1. Build a JSON payload with the request path and a nonce
  2. Base64-encode the payload → X-GEMINI-PAYLOAD header
  3. HMAC-SHA384 sign the base64 string with your secret → X-GEMINI-SIGNATURE header
  4. Send the API key in X-GEMINI-APIKEY

See the API key authentication docs for the full protocol specification.

OAuth — confidential server client

Use OAuthAuth when your server acts on behalf of a user who has authorized your application. Confidential clients have a client_secret.

Code
import { createClient, OAuthAuth } from "gemini-markets/server"; const auth = new OAuthAuth({ client: { type: "confidential", clientId: process.env.OAUTH_CLIENT_ID, clientSecret: process.env.OAUTH_CLIENT_SECRET, redirectUri: "https://yourapp.com/callback", }, env: "sandbox", tokenStore: myTokenStore, // you implement this });

Authorization flow

OAuth requires a multi-step flow: redirect the user, receive a callback, exchange the code for tokens.

Code
// Step 1: Generate the authorization URL const { url, transaction } = await auth.beginAuthorization([ "orders:create", "orders:read", "balances:read", ]); // Step 2: Redirect the user to `url` // They log in at Gemini and authorize your app // Step 3: Handle the callback const tokens = await auth.completeAuthorization(callbackUrl, transaction); // Step 4: Use the authenticated client const client = await createClient({ env: "sandbox", auth }); const positions = await client.predictions.getPositions({ limit: 10 });

Implementing a token store

The SDK does not persist tokens — you provide a tokenStore that handles storage. The store must implement four methods:

Code
interface OAuthTokenStore { load(): Promise<OAuthTokens | undefined>; save(tokens: OAuthTokens): Promise<void>; clear(): Promise<void>; runExclusive<T>(operation: () => Promise<T>): Promise<T>; }

runExclusive must serialize all token operations. If you run multiple server instances, this must use a distributed lock (e.g., Redis, database row lock). Single-use refresh tokens will fail if two instances try to refresh concurrently.

A minimal in-memory implementation for development:

Code
class MemoryTokenStore { private tokens?: OAuthTokens; async load() { return this.tokens; } async save(tokens: OAuthTokens) { this.tokens = tokens; } async clear() { this.tokens = undefined; } async runExclusive<T>(op: () => Promise<T>) { return op(); } }

Token refresh

Access tokens expire after 24 hours. The SDK refreshes them automatically when credentialHeaders() detects an expired token. Refresh happens inside runExclusive to prevent concurrent rotation of single-use refresh tokens.

You can tune refresh timing:

Code
new OAuthAuth({ // ... refreshSkewMs: 60_000, // refresh 60s before expiry (default) });

Revocation

Revoke tokens explicitly when a user disconnects your app:

Code
import { HttpTransport } from "gemini-markets/server"; const transport = new HttpTransport({ env: "sandbox", auth }); await auth.revoke(transport); // tokens are cleared from the store after successful server-side revocation

Browser OAuth (PKCE)

Browser apps cannot hold a client secret. Use BrowserOAuthAuth which enforces public-client PKCE at the type level — you cannot accidentally pass a clientSecret.

Code
// login.ts — the page that starts the OAuth flow import { createClient, BrowserOAuthAuth } from "gemini-markets/browser"; const auth = new BrowserOAuthAuth({ client: { type: "public", clientId: "your-client-id", redirectUri: "http://localhost:3000/callback", }, env: "sandbox", tokenStore: myBrowserTokenStore, }); // Step 1: Start authorization (generates PKCE challenge automatically) const { url, transaction } = await auth.beginAuthorization(["orders:read"]); // Step 2: Persist the transaction — the redirect will lose in-memory state sessionStorage.setItem("gemini_oauth_tx", JSON.stringify(transaction)); // Step 3: Redirect the user window.location.href = url;
Code
// callback.ts — the page Gemini redirects back to const raw = sessionStorage.getItem("gemini_oauth_tx"); if (!raw) throw new Error("OAuth transaction not found — was the flow started?"); const transaction = JSON.parse(raw); sessionStorage.removeItem("gemini_oauth_tx"); // Step 4: Complete the exchange with the saved transaction await auth.completeAuthorization(window.location.href, transaction); // Step 5: Use the client const client = createClient({ auth }); const events = await client.predictions.listEvents({ status: ["active"] });

The PKCE code challenge and verifier are generated automatically using Web Crypto (crypto.subtle). The verifier is sent during code exchange — no secret ever leaves the browser.

Scopes

Scopes control what the OAuth token can access. Request only what your app needs:

ScopeGrants
orders:createPlace and cancel orders
orders:readView orders and trade history
balances:readView account balances
addresses:readView approved withdrawal addresses
history:readView transaction history

The full scope list is in the OAuth documentation.

Browser token persistence

In the browser, persist tokens to localStorage or sessionStorage:

Code
const browserTokenStore = { async load() { const raw = localStorage.getItem("gemini_tokens"); return raw ? JSON.parse(raw) : undefined; }, async save(tokens) { localStorage.setItem("gemini_tokens", JSON.stringify(tokens)); }, async clear() { localStorage.removeItem("gemini_tokens"); }, async runExclusive(op) { return op(); }, };

For production apps, consider encrypting tokens at rest and using sessionStorage for shorter-lived sessions.

What's next

  • WebSocket — real-time streams with authenticated access
  • Error handling — OAuth-specific error types and recovery
Last modified on August 14, 2026
QuickstartWebSocket
On this page
  • HMAC authentication
    • Nonce modes
    • How HMAC signing works
  • OAuth — confidential server client
    • Authorization flow
    • Implementing a token store
    • Token refresh
    • Revocation
  • Browser OAuth (PKCE)
    • Scopes
    • Browser token persistence
  • What's next
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript