A framework-agnostic TypeScript client for the TopstepX trading API with REST and SignalR data feeds via WebSocket.
- Full REST API coverage (accounts, orders, positions, trades, contracts, history)
- Real-time WebSocket data via SignalR (quotes, trades, market depth)
- Automatic token management and refresh
- TypeScript-first with complete type definitions
- Works with any Node.js framework (Express, Fastify, NestJS, etc.)
- Dual ESM/CommonJS support
npm install topstepx-apiCreate a .env file in your project root:
TOPSTEP_USERNAME=your_usernameTOPSTEP_API_KEY=your_api_keyGet your API key from your TopstepX account settings.
import{TopstepXClient,OrderTypeEnum,OrderSideEnum}from'topstepx-api';constclient=newTopstepXClient({username: process.env.TOPSTEP_USERNAME!,apiKey: process.env.TOPSTEP_API_KEY!,});awaitclient.connect();// Get accountsconstresponse=awaitclient.accounts.search({onlyActiveAccounts: true});console.log('Accounts:',response.accounts);// Place a market orderconstorder=awaitclient.orders.place({accountId: response.accounts[0].id,contractId: 'CON.F.US.ENQ.M25',type: OrderTypeEnum.Market,side: OrderSideEnum.Buy,size: 1,});console.log('Order placed:',order.orderId);// Disconnect when doneawaitclient.disconnect();The main client class that provides access to all APIs.
constclient=newTopstepXClient({username: string;// Required: TopstepX username
apiKey: string;// Required: TopstepX API key
baseUrl?: string;// Optional: API base URL (default: https://api.topstepx.com)
marketHubUrl?: string;// Optional: Market WebSocket URL
userHubUrl?: string;// Optional: User WebSocket URL
autoRefresh?: boolean;// Optional: Auto-refresh tokens (default: true)
tokenValidityHours?: number;// Optional: Token validity period (default: 24)});| Method | Description |
|---|---|
connect() | Authenticate and establish WebSocket connections |
disconnect() | Close all connections and cleanup |
getToken() | Get the current session token |
isConnected | Check if WebSocket connections are active |
client.on('connected',()=>console.log('Connected'));client.on('disconnected',()=>console.log('Disconnected'));client.on('error',(error)=>console.error('Error:',error));Access via client.accounts
Search for accounts.
constresponse=awaitclient.accounts.search({onlyActiveAccounts: boolean;});// Returns: SearchAccountsResponseInterfaceinterfaceSearchAccountsResponseInterface{accounts: AccountInterface[];success: boolean;errorCode: number;errorMessage: string|null;}interfaceAccountInterface{id: number;name: string;canTrade: boolean;isVisible: boolean;}Access via client.orders
Place a new order.
import{OrderTypeEnum,OrderSideEnum}from'topstepx-api';constresult=awaitclient.orders.place({accountId: number;
contractId: string;
type: OrderTypeEnum;// Market, Limit, Stop, StopLimit
side: OrderSideEnum;// Buy, Sell
size: number;
limitPrice?: number;// Required for Limit/StopLimit
stopPrice?: number;// Required for Stop/StopLimit
trailPrice?: number;// Optional trailing stop price
customTag?: string;// Optional custom identifier
linkedOrderId?: number;// Optional linked order (OCO)});// Returns: PlaceOrderResponseInterfaceinterfacePlaceOrderResponseInterface{orderId: number;success: boolean;errorCode: number;errorMessage: string|null;}Cancel an existing order.
constresponse=awaitclient.orders.cancel({accountId: number;
orderId: number;});// Returns: CancelOrderResponseInterfaceinterfaceCancelOrderResponseInterface{success: boolean;errorCode: number;errorMessage: string|null;}Modify an existing order.
constresponse=awaitclient.orders.modify({accountId: number;
orderId: number;
size?: number;
limitPrice?: number;
stopPrice?: number;
trailPrice?: number;});// Returns: ModifyOrderResponseInterfaceinterfaceModifyOrderResponseInterface{success: boolean;errorCode: number;errorMessage: string|null;}Search historical orders.
constresponse=awaitclient.orders.search({accountId: number;startTimestamp?: string;// ISO 8601 formatendTimestamp?: string;});// Returns: SearchOrdersResponseInterfaceinterfaceSearchOrdersResponseInterface{orders: OrderInterface[];success: boolean;errorCode: number;errorMessage: string|null;}interfaceOrderInterface{id: number;accountId: number;contractId: string;creationTimestamp: string;updateTimestamp: string|null;status: OrderStatusEnum;type: OrderTypeEnum;side: OrderSideEnum;size: number;limitPrice: number|null;stopPrice: number|null;}Get currently open orders.
constresponse=awaitclient.orders.searchOpen({accountId: number;});// Returns: SearchOpenOrdersResponseInterfaceinterfaceSearchOpenOrdersResponseInterface{orders: OrderInterface[];success: boolean;errorCode: number;errorMessage: string|null;}Access via client.positions
Get open positions.
constresponse=awaitclient.positions.searchOpen({accountId: number;});// Returns: SearchOpenPositionsResponseInterfaceinterfaceSearchOpenPositionsResponseInterface{positions: PositionInterface[];success: boolean;errorCode: number;errorMessage: string|null;}interfacePositionInterface{id: number;accountId: number;contractId: string;creationTimestamp: string;type: PositionTypeEnum;// Long, Shortsize: number;averagePrice: number;}Close a position entirely.
constresponse=awaitclient.positions.close({accountId: number;
contractId: string;});// Returns: ClosePositionResponseInterfaceinterfaceClosePositionResponseInterface{success: boolean;errorCode: number;errorMessage: string|null;}Partially close a position.
constresponse=awaitclient.positions.partialClose({accountId: number;
contractId: string;
size: number;// Number of contracts to close});// Returns: PartialClosePositionResponseInterfaceinterfacePartialClosePositionResponseInterface{success: boolean;errorCode: number;errorMessage: string|null;}Access via client.trades
Search trade history.
constresponse=awaitclient.trades.search({accountId: number;
startTimestamp: string;// ISO 8601 format
endTimestamp: string;});// Returns: SearchTradesResponseInterfaceinterfaceSearchTradesResponseInterface{trades: TradeInterface[];success: boolean;errorCode: number;errorMessage: string|null;}interfaceTradeInterface{id: number;accountId: number;contractId: string;creationTimestamp: string;price: number;profitAndLoss: number|null;fees: number;side: OrderSideEnum;size: number;voided: boolean;orderId: number;}Access via client.contracts
Search for contracts/symbols.
constresponse=awaitclient.contracts.search({searchText: string;// e.g., "ES", "NQ", "CL"
live: boolean;// true for live, false for sim});// Returns: SearchContractsResponseInterfaceinterfaceSearchContractsResponseInterface{contracts: ContractInterface[];success: boolean;errorCode: number;errorMessage: string|null;}interfaceContractInterface{id: string;name: string;description: string;tickSize: number;tickValue: number;activeContract: boolean;}Get a specific contract by ID.
constresponse=awaitclient.contracts.searchById({contractId: string;// e.g., "CON.F.US.ENQ.M25"
live: boolean;});// Returns: SearchContractByIdResponseInterfaceinterfaceSearchContractByIdResponseInterface{contract: ContractInterface|null;success: boolean;errorCode: number;errorMessage: string|null;}Access via client.history
Get historical OHLCV bars.
import{BarUnitEnum}from'topstepx-api';constresponse=awaitclient.history.retrieveBars({contractId: string;
live: boolean;
startTime: string;// ISO 8601 format
endTime: string;
unit: BarUnitEnum;// Second, Minute, Hour, Day, Week, Month
unitNumber: number;// e.g., 5 for 5-minute bars
limit: number;// Max bars to return
includePartialBar: boolean;});// Returns: RetrieveBarsResponseInterfaceinterfaceRetrieveBarsResponseInterface{bars: BarInterface[];success: boolean;errorCode: number;errorMessage: string|null;}interfaceBarInterface{t: string;// timestampo: number;// openh: number;// highl: number;// lowc: number;// closev: number;// volume}Access via client.marketHub
Subscribe to real-time market data via WebSocket.
// Subscribe to all market data for a contractawaitclient.marketHub.subscribe('CON.F.US.ENQ.M25');// Or subscribe selectivelyawaitclient.marketHub.subscribeQuotes('CON.F.US.ENQ.M25');awaitclient.marketHub.subscribeTrades('CON.F.US.ENQ.M25');awaitclient.marketHub.subscribeDepth('CON.F.US.ENQ.M25');awaitclient.marketHub.unsubscribe('CON.F.US.ENQ.M25');// Or unsubscribe selectivelyawaitclient.marketHub.unsubscribeQuotes('CON.F.US.ENQ.M25');awaitclient.marketHub.unsubscribeTrades('CON.F.US.ENQ.M25');awaitclient.marketHub.unsubscribeDepth('CON.F.US.ENQ.M25');// Quote updatesclient.marketHub.on('quote',({ contractId, data })=>{for(constquoteofdata){console.log(`${contractId}: Bid ${quote.bestBid} / Ask ${quote.bestAsk}`);}});// Trade updatesclient.marketHub.on('trade',({ contractId, data })=>{for(consttradeofdata){console.log(`${contractId}: ${trade.volume} @ ${trade.price}`);}});// Market depth updatesclient.marketHub.on('depth',({ contractId, data })=>{for(constlevelofdata){console.log(`${contractId}: ${level.volume} @ ${level.price}`);}});interfaceRealtimeMarketQuoteEventInterface{symbol: string;lastPrice: number;bestBid: number;bestAsk: number;change: number;changePercent: number;volume: number;lastUpdated: string;timestamp: string;}interfaceRealtimeMarketTradeEventInterface{symbolId: string;price: number;timestamp: string;type: 0|1;// 0 = Bid, 1 = Askvolume: number;}interfaceRealtimeMarketDepthEventInterface{price: number;volume: number;currentVolume: number;type: number;timestamp: string;}Access via client.userHub
Subscribe to real-time account updates via WebSocket.
// Subscribe to all account updatesawaitclient.userHub.subscribe(accountId);// Or subscribe selectivelyawaitclient.userHub.subscribeOrders(accountId);awaitclient.userHub.subscribePositions(accountId);awaitclient.userHub.subscribeTrades(accountId);awaitclient.userHub.unsubscribe(accountId);// Order updatesclient.userHub.on('order',(order)=>{console.log(`Order ${order.id}: ${order.status}`);});// Position updatesclient.userHub.on('position',(position)=>{console.log(`Position: ${position.size} contracts @ ${position.averagePrice}`);});// Trade executionsclient.userHub.on('trade',(trade)=>{console.log(`Trade: ${trade.size} @ ${trade.price}, P&L: ${trade.profitAndLoss}`);});// Account updatesclient.userHub.on('account',(account)=>{console.log(`Account ${account.name}: Can trade = ${account.canTrade}`);});import{OrderTypeEnum,OrderSideEnum,OrderStatusEnum,BarUnitEnum,PositionTypeEnum,TradeTypeEnum,}from'topstepx-api';// OrderTypeEnumOrderTypeEnum.Limit// 1OrderTypeEnum.Market// 2OrderTypeEnum.Stop// 3OrderTypeEnum.StopLimit// 4// OrderSideEnumOrderSideEnum.Buy// 0OrderSideEnum.Sell// 1// OrderStatusEnumOrderStatusEnum.Pending// 0OrderStatusEnum.Working// 1OrderStatusEnum.Filled// 2OrderStatusEnum.Cancelled// 3OrderStatusEnum.Rejected// 4OrderStatusEnum.PartiallyFilled// 5// BarUnitEnumBarUnitEnum.Second// 1BarUnitEnum.Minute// 2BarUnitEnum.Hour// 3BarUnitEnum.Day// 4BarUnitEnum.Week// 5BarUnitEnum.Month// 6// PositionTypeEnumPositionTypeEnum.Long// 0PositionTypeEnum.Short// 1The library provides typed errors for different failure scenarios:
import{TopstepXError,AuthenticationError,ApiError,ConnectionError,}from'topstepx-api';try{awaitclient.connect();awaitclient.orders.place({ ... });}catch(error){if(errorinstanceofAuthenticationError){console.error('Auth failed:',error.message,error.code);}elseif(errorinstanceofApiError){console.error(`API error on ${error.endpoint}:`,error.message);}elseif(errorinstanceofConnectionError){console.error('WebSocket error:',error.message);}}All errors extend TopstepXError and include:
message- Error descriptioncode- Error code (if applicable)timestamp- When the error occurredtoJSON()- Serialize for logging
import{TopstepXClient,OrderTypeEnum,OrderSideEnum,BarUnitEnum,ApiError,}from'topstepx-api';import'dotenv/config';asyncfunctionmain(){constclient=newTopstepXClient({username: process.env.TOPSTEP_USERNAME!,apiKey: process.env.TOPSTEP_API_KEY!,});try{// Connectawaitclient.connect();console.log('Connected to TopstepX');// Get accountsconstaccountsResponse=awaitclient.accounts.search({onlyActiveAccounts: true});constaccount=accountsResponse.accounts[0];console.log(`Using account: ${account.name} (${account.id})`);// Search for ES contractconstcontractsResponse=awaitclient.contracts.search({searchText: 'ES',live: false,});constesContract=contractsResponse.contracts.find(c=>c.activeContract);console.log(`Found contract: ${esContract?.id}`);// Get historical dataconstendTime=newDate();conststartTime=newDate(endTime.getTime()-24*60*60*1000);constbarsResponse=awaitclient.history.retrieveBars({contractId: esContract!.id,live: false,startTime: startTime.toISOString(),endTime: endTime.toISOString(),unit: BarUnitEnum.Hour,unitNumber: 1,limit: 24,includePartialBar: false,});console.log(`Retrieved ${barsResponse.bars.length} hourly bars`);// Subscribe to real-time quotesclient.marketHub.on('quote',({ contractId, data })=>{constquote=data[0];console.log(`${contractId}: ${quote.bestBid} / ${quote.bestAsk}`);});awaitclient.marketHub.subscribeQuotes(esContract!.id);// Subscribe to account updatesclient.userHub.on('order',(order)=>{console.log(`Order update: ${order.id} - ${order.status}`);});awaitclient.userHub.subscribe(account.id);// Place a limit orderconstorder=awaitclient.orders.place({accountId: account.id,contractId: esContract!.id,type: OrderTypeEnum.Limit,side: OrderSideEnum.Buy,size: 1,limitPrice: barsResponse.bars[barsResponse.bars.length-1].c-10,// 10 points below last close});console.log(`Placed order: ${order.orderId}`);// Check open ordersconstopenOrdersResponse=awaitclient.orders.searchOpen({accountId: account.id});console.log(`Open orders: ${openOrdersResponse.orders.length}`);// Cancel the orderawaitclient.orders.cancel({accountId: account.id,orderId: order.orderId,});console.log('Order cancelled');// Keep running for real-time updatesawaitnewPromise(resolve=>setTimeout(resolve,30000));}catch(error){if(errorinstanceofApiError){console.error(`API Error [${error.endpoint}]: ${error.message}`);}else{console.error('Error:',error);}}finally{awaitclient.disconnect();console.log('Disconnected');}}main();All types are exported for full TypeScript support:
importtype{// ConfigTopstepXClientConfig,TopstepXClientEvents,// REST domain modelsAccountInterface,OrderInterface,PositionInterface,TradeInterface,ContractInterface,BarInterface,// Response interfacesSearchAccountsResponseInterface,PlaceOrderResponseInterface,CancelOrderResponseInterface,ModifyOrderResponseInterface,SearchOrdersResponseInterface,SearchOpenOrdersResponseInterface,SearchOpenPositionsResponseInterface,ClosePositionResponseInterface,PartialClosePositionResponseInterface,SearchTradesResponseInterface,SearchContractsResponseInterface,SearchContractByIdResponseInterface,RetrieveBarsResponseInterface,// Request interfacesPlaceOrderRequestInterface,ModifyOrderRequestInterface,SearchOrdersRequestInterface,RetrieveBarsRequestInterface,// WebSocket interfacesRealtimeMarketQuoteEventInterface,RealtimeMarketTradeEventInterface,RealtimeMarketDepthEventInterface,RealtimeUserOrderUpdateInterface,RealtimeUserPositionUpdateInterface,RealtimeUserTradeUpdateInterface,}from'topstepx-api';MIT